From 0fb0d234d6a747481395f395ae5d51e5f8812596 Mon Sep 17 00:00:00 2001 From: William Petit Date: Tue, 7 Mar 2023 23:10:42 +0100 Subject: [PATCH] feat: authenticate users and agents requests --- .gitignore | 4 +- Makefile | 5 +- go.mod | 3 +- go.sum | 54 +----------- internal/agent/agent.go | 23 +++-- internal/auth/agent/authenticator.go | 92 ++++++++++++++++++++ internal/auth/agent/jwt.go | 37 ++++++++ internal/auth/agent/user.go | 23 +++++ internal/auth/middleware.go | 82 +++++++++++++++++ internal/auth/user/authenticator.go | 67 ++++++++++++++ internal/auth/user/jwt.go | 61 +++++++++++++ internal/auth/user/user.go | 32 +++++++ internal/client/client.go | 53 +++++++---- internal/client/get_agent.go | 4 +- internal/client/get_agent_specs.go | 4 +- internal/client/options.go | 24 +++++ internal/client/query_agents.go | 13 ++- internal/client/register_agent.go | 4 +- internal/client/update_agent.go | 15 +++- internal/client/update_agent_spec.go | 4 +- internal/command/agent/run.go | 1 - internal/command/client/agent/get.go | 7 +- internal/command/client/agent/query.go | 8 +- internal/command/client/agent/update.go | 7 +- internal/command/client/flag/flag.go | 42 +++++++++ internal/command/server/auth/create_token.go | 54 ++++++++++++ internal/command/server/auth/root.go | 15 ++++ internal/command/server/database/migrate.go | 4 +- internal/command/server/database/ping.go | 8 +- internal/command/server/database/reset.go | 4 +- internal/command/server/root.go | 2 + internal/command/server/run.go | 2 +- internal/config/config.go | 16 ++-- internal/config/server.go | 19 ++++ internal/server/init.go | 2 +- internal/server/option.go | 10 ++- internal/server/server.go | 43 +++++++-- internal/setup/repository.go | 6 +- modd.conf | 3 +- 39 files changed, 726 insertions(+), 131 deletions(-) create mode 100644 internal/auth/agent/authenticator.go create mode 100644 internal/auth/agent/jwt.go create mode 100644 internal/auth/agent/user.go create mode 100644 internal/auth/middleware.go create mode 100644 internal/auth/user/authenticator.go create mode 100644 internal/auth/user/jwt.go create mode 100644 internal/auth/user/user.go create mode 100644 internal/client/options.go create mode 100644 internal/command/server/auth/create_token.go create mode 100644 internal/command/server/auth/root.go create mode 100644 internal/config/server.go diff --git a/.gitignore b/.gitignore index 1dec8f4..46dc536 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ dist/ /emissary.sqlite /.gitea-release /agent-key.json -/apps \ No newline at end of file +/apps +/server-key.json +/.emissary-token \ No newline at end of file diff --git a/Makefile b/Makefile index 55eeb45..2c61cab 100644 --- a/Makefile +++ b/Makefile @@ -137,4 +137,7 @@ gitea-release: tools/gitea-release/bin/gitea-release.sh goreleaser tools/gitea-release/bin/gitea-release.sh: mkdir -p tools/gitea-release/bin curl --output tools/gitea-release/bin/gitea-release.sh https://forge.cadoles.com/Cadoles/Jenkins/raw/branch/master/resources/com/cadoles/gitea/gitea-release.sh - chmod +x tools/gitea-release/bin/gitea-release.sh \ No newline at end of file + chmod +x tools/gitea-release/bin/gitea-release.sh + +.emissary-token: + $(MAKE) run-emissary-server EMISSARY_CMD="--debug --config tmp/server.yml server auth create-token > .emissary-token" \ No newline at end of file diff --git a/go.mod b/go.mod index 34bb823..68670d3 100644 --- a/go.mod +++ b/go.mod @@ -33,11 +33,10 @@ require ( github.com/gabriel-vasile/mimetype v1.4.1 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/hashicorp/go.net v0.0.1 // indirect github.com/hashicorp/mdns v1.0.5 // indirect github.com/igm/sockjs-go/v3 v3.0.2 // indirect + github.com/lithammer/shortuuid/v4 v4.0.0 // indirect github.com/miekg/dns v1.1.51 // indirect github.com/oklog/ulid/v2 v2.1.0 // indirect github.com/orcaman/concurrent-map v1.0.0 // indirect diff --git a/go.sum b/go.sum index 34b76e3..f6f1465 100644 --- a/go.sum +++ b/go.sum @@ -54,12 +54,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -forge.cadoles.com/arcad/edge v0.0.0-20230303104220-0bb7f2cd8500 h1:TKjlNgXdxxTUXRY1EzwaY+hcjoPbrUg3z+iw2Wvk/eA= -forge.cadoles.com/arcad/edge v0.0.0-20230303104220-0bb7f2cd8500/go.mod h1:pl9EMtSLSVr4wbDgQBDjr4aizwtmwasE686dm5arYPw= -forge.cadoles.com/arcad/edge v0.0.0-20230303145523-a9d2c282f2e0 h1:mLOkZAa9/anqFY22uolxOjeBnAv5b6fpLEEJ4aXn2d0= -forge.cadoles.com/arcad/edge v0.0.0-20230303145523-a9d2c282f2e0/go.mod h1:pl9EMtSLSVr4wbDgQBDjr4aizwtmwasE686dm5arYPw= -forge.cadoles.com/arcad/edge v0.0.0-20230303153324-fef03214759c h1:xmTr/6Ehh/9gmPzeK9zlDdoqc272Q7EctqXs8Y78LY0= -forge.cadoles.com/arcad/edge v0.0.0-20230303153324-fef03214759c/go.mod h1:pl9EMtSLSVr4wbDgQBDjr4aizwtmwasE686dm5arYPw= forge.cadoles.com/arcad/edge v0.0.0-20230303153719-6399196fe5c9 h1:dleaaf/rV2GWtGvrPunRakjrKVDfXoANxAy8/1ctMVs= forge.cadoles.com/arcad/edge v0.0.0-20230303153719-6399196fe5c9/go.mod h1:pl9EMtSLSVr4wbDgQBDjr4aizwtmwasE686dm5arYPw= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= @@ -423,8 +417,6 @@ github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.8.0 h1:rJD5HeGIT/2b5CDk63FVCwZA3qgYElfg+oQK7uH5pfE= -github.com/dlclark/regexp2 v1.8.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= @@ -458,7 +450,6 @@ github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8 github.com/dop251/goja_nodejs v0.0.0-20230226152057-060fa99b809f h1:mmnNidRg3cMfcgyeNtIBSDZgjf/85lA/2pplccwSxYg= github.com/dop251/goja_nodejs v0.0.0-20230226152057-060fa99b809f/go.mod h1:0tlktQL7yHfYEtjcRGi/eiOkbDR5XF7gyFFvbC5//E0= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -483,8 +474,6 @@ github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -608,8 +597,6 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-migrate/migrate/v4 v4.15.2 h1:vU+M05vs6jWHKDdmE1Ecwj0BznygFc4QsdRe2E/L7kc= @@ -758,7 +745,6 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.0-20151006203346-104dcad90073/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -827,8 +813,6 @@ github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXg github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA= -github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8= -github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk= github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -836,8 +820,6 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle/v2 v2.1.2 h1:0f7vaaXINONKTsxYDn4otOAiJanX/BMeAtY//BXqzlg= -github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels= github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk= github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jedib0t/go-pretty/v6 v6.4.4 h1:N+gz6UngBPF4M288kiMURPHELDMIhF/Em35aYuKrsSc= @@ -931,6 +913,8 @@ github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= +github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c= +github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -949,7 +933,6 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= @@ -961,7 +944,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -974,7 +956,6 @@ github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lL github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -1032,7 +1013,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= @@ -1157,7 +1137,6 @@ github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0= github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -1269,10 +1248,6 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY= -github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= -github.com/urfave/cli/v2 v2.24.3 h1:7Q1w8VN8yE0MJEHP06bv89PjYsN4IHWED2s1v/Zlfm0= -github.com/urfave/cli/v2 v2.24.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/urfave/cli/v2 v2.24.4 h1:0gyJJEBYtCV87zI/x2nZCPyDxD51K6xM8SkwjHFCNEU= github.com/urfave/cli/v2 v2.24.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -1398,8 +1373,6 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1449,7 +1422,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= @@ -1529,8 +1501,6 @@ golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1564,8 +1534,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= -golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1697,8 +1665,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1708,8 +1674,6 @@ golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1723,8 +1687,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1814,7 +1776,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= @@ -2116,8 +2077,6 @@ modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v1.9.5/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= -modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= -modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= @@ -2127,8 +2086,6 @@ modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6 modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= -modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= -modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= @@ -2137,21 +2094,14 @@ modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs= -modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs= -modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= -modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE= -modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo= -modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34= modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= -modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= diff --git a/internal/agent/agent.go b/internal/agent/agent.go index a3a7f6c..68b02b1 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -5,6 +5,7 @@ import ( "time" "forge.cadoles.com/Cadoles/emissary/internal/agent/metadata" + "forge.cadoles.com/Cadoles/emissary/internal/auth/agent" "forge.cadoles.com/Cadoles/emissary/internal/client" "forge.cadoles.com/Cadoles/emissary/internal/jwk" "github.com/pkg/errors" @@ -15,7 +16,7 @@ import ( type Agent struct { thumbprint string privateKey jwk.Key - client *client.Client + serverURL string controllers []Controller interval time.Duration collectors []metadata.Collector @@ -29,7 +30,15 @@ func (a *Agent) Run(ctx context.Context) error { ticker := time.NewTicker(a.interval) defer ticker.Stop() - ctx = withClient(ctx, a.client) + logger.Info(ctx, "generating token") + token, err := agent.GenerateToken(a.privateKey, a.thumbprint) + if err != nil { + return errors.WithStack(err) + } + + client := client.New(a.serverURL, client.WithToken(token)) + + ctx = withClient(ctx, client) for { select { @@ -37,7 +46,7 @@ func (a *Agent) Run(ctx context.Context) error { logger.Debug(ctx, "registering agent") - if err := a.registerAgent(ctx, state); err != nil { + if err := a.registerAgent(ctx, client, state); err != nil { logger.Error(ctx, "could not register agent", logger.E(errors.WithStack(err))) continue @@ -76,7 +85,7 @@ func (a *Agent) Reconcile(ctx context.Context, state *State) error { return nil } -func (a *Agent) registerAgent(ctx context.Context, state *State) error { +func (a *Agent) registerAgent(ctx context.Context, client *client.Client, state *State) error { meta, err := a.collectMetadata(ctx) if err != nil { return errors.WithStack(err) @@ -84,7 +93,7 @@ func (a *Agent) registerAgent(ctx context.Context, state *State) error { sorted := metadata.Sort(meta) - agent, err := a.client.RegisterAgent(ctx, a.privateKey, a.thumbprint, sorted) + agent, err := client.RegisterAgent(ctx, a.privateKey, a.thumbprint, sorted) if err != nil { return errors.WithStack(err) } @@ -129,12 +138,10 @@ func New(serverURL string, privateKey jwk.Key, thumbprint string, funcs ...Optio fn(opt) } - client := client.New(serverURL) - return &Agent{ + serverURL: serverURL, privateKey: privateKey, thumbprint: thumbprint, - client: client, controllers: opt.Controllers, interval: opt.Interval, collectors: opt.Collectors, diff --git a/internal/auth/agent/authenticator.go b/internal/auth/agent/authenticator.go new file mode 100644 index 0000000..52bf5c0 --- /dev/null +++ b/internal/auth/agent/authenticator.go @@ -0,0 +1,92 @@ +package agent + +import ( + "context" + "net/http" + "strings" + + "forge.cadoles.com/Cadoles/emissary/internal/auth" + "forge.cadoles.com/Cadoles/emissary/internal/datastore" + "github.com/lestrrat-go/jwx/v2/jws" + "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +type Authenticator struct { + repo datastore.AgentRepository +} + +// Authenticate implements auth.Authenticator. +func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth.User, error) { + ctx = logger.With(r.Context(), logger.F("remoteAddr", r.RemoteAddr)) + + authorization := r.Header.Get("Authorization") + if authorization == "" { + return nil, errors.WithStack(auth.ErrUnauthenticated) + } + + rawToken := strings.TrimPrefix(authorization, "Bearer ") + if rawToken == "" { + return nil, errors.WithStack(auth.ErrUnauthenticated) + } + + token, err := jwt.Parse([]byte(rawToken), jwt.WithVerify(false)) + if err != nil { + return nil, errors.WithStack(err) + } + + rawThumbprint, exists := token.Get(keyThumbprint) + if !exists { + return nil, errors.Errorf("could not find '%s' claim", keyThumbprint) + } + + thumbrint, ok := rawThumbprint.(string) + if !ok { + return nil, errors.Errorf("unexpected '%s' claim value: '%v'", keyThumbprint, rawThumbprint) + } + + agents, _, err := a.repo.Query( + ctx, + datastore.WithAgentQueryThumbprints(thumbrint), + datastore.WithAgentQueryLimit(1), + ) + if err != nil { + return nil, errors.WithStack(err) + } + + if len(agents) != 1 { + return nil, errors.Errorf("unexpected number of found agents: '%d'", len(agents)) + } + + agent, err := a.repo.Get( + ctx, + agents[0].ID, + ) + if err != nil { + return nil, errors.WithStack(err) + } + + _, err = jwt.Parse( + []byte(rawToken), + jwt.WithKeySet(agent.KeySet.Set, jws.WithRequireKid(false)), + jwt.WithValidate(true), + ) + if err != nil { + return nil, errors.WithStack(err) + } + + user := &User{ + agent: agent, + } + + return user, nil +} + +func NewAuthenticator(repo datastore.AgentRepository) *Authenticator { + return &Authenticator{ + repo: repo, + } +} + +var _ auth.Authenticator = &Authenticator{} diff --git a/internal/auth/agent/jwt.go b/internal/auth/agent/jwt.go new file mode 100644 index 0000000..b209d40 --- /dev/null +++ b/internal/auth/agent/jwt.go @@ -0,0 +1,37 @@ +package agent + +import ( + "time" + + "forge.cadoles.com/Cadoles/emissary/internal/jwk" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/pkg/errors" +) + +const keyThumbprint = "thumbprint" + +func GenerateToken(key jwk.Key, thumbprint string) (string, error) { + token := jwt.New() + + if err := token.Set(keyThumbprint, thumbprint); err != nil { + return "", errors.WithStack(err) + } + + now := time.Now() + + if err := token.Set(jwt.NotBeforeKey, now); err != nil { + return "", errors.WithStack(err) + } + + if err := token.Set(jwt.IssuedAtKey, now); err != nil { + return "", errors.WithStack(err) + } + + rawToken, err := jwt.Sign(token, jwt.WithKey(jwa.RS256, key)) + if err != nil { + return "", errors.WithStack(err) + } + + return string(rawToken), nil +} diff --git a/internal/auth/agent/user.go b/internal/auth/agent/user.go new file mode 100644 index 0000000..8f23e6f --- /dev/null +++ b/internal/auth/agent/user.go @@ -0,0 +1,23 @@ +package agent + +import ( + "fmt" + + "forge.cadoles.com/Cadoles/emissary/internal/auth" + "forge.cadoles.com/Cadoles/emissary/internal/datastore" +) + +type User struct { + agent *datastore.Agent +} + +// Subject implements auth.User +func (u *User) Subject() string { + return fmt.Sprintf("agent-%d", u.agent.ID) +} + +func (u *User) Agent() *datastore.Agent { + return u.agent +} + +var _ auth.User = &User{} diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go new file mode 100644 index 0000000..13e5b60 --- /dev/null +++ b/internal/auth/middleware.go @@ -0,0 +1,82 @@ +package auth + +import ( + "context" + "net/http" + + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/api" + "gitlab.com/wpetit/goweb/logger" +) + +const ( + ErrCodeUnauthorized api.ErrorCode = "unauthorized" + ErrCodeForbidden api.ErrorCode = "forbidden" +) + +type contextKey string + +const ( + contextKeyUser contextKey = "user" +) + +func CtxUser(ctx context.Context) (*User, error) { + user, ok := ctx.Value(contextKeyUser).(*User) + if !ok { + return nil, errors.Errorf("unexpected user type: expected '%T', got '%T'", new(User), ctx.Value(contextKeyUser)) + } + + return user, nil +} + +var ( + ErrUnauthenticated = errors.New("unauthenticated") + ErrForbidden = errors.New("forbidden") +) + +type User interface { + Subject() string +} + +type Authenticator interface { + Authenticate(context.Context, *http.Request) (User, error) +} + +func Middleware(authenticators ...Authenticator) func(http.Handler) http.Handler { + return func(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + ctx := logger.With(r.Context(), logger.F("remoteAddr", r.RemoteAddr)) + + var ( + user User + err error + ) + + for _, auth := range authenticators { + user, err = auth.Authenticate(ctx, r) + if err != nil { + logger.Warn(ctx, "could not authenticate request", logger.E(errors.WithStack(err))) + + continue + } + + if user != nil { + break + } + } + + if user == nil { + api.ErrorResponse(w, http.StatusUnauthorized, ErrCodeUnauthorized, nil) + + return + } + + ctx = logger.With(ctx, logger.F("user", user.Subject())) + ctx = context.WithValue(ctx, contextKeyUser, user) + + h.ServeHTTP(w, r.WithContext(ctx)) + } + + return http.HandlerFunc(fn) + } +} diff --git a/internal/auth/user/authenticator.go b/internal/auth/user/authenticator.go new file mode 100644 index 0000000..5b5f781 --- /dev/null +++ b/internal/auth/user/authenticator.go @@ -0,0 +1,67 @@ +package user + +import ( + "context" + "net/http" + "strings" + + "forge.cadoles.com/Cadoles/emissary/internal/auth" + "forge.cadoles.com/Cadoles/emissary/internal/jwk" + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +type Authenticator struct { + keys jwk.Set + issuer string +} + +// Authenticate implements auth.Authenticator. +func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth.User, error) { + ctx = logger.With(r.Context(), logger.F("remoteAddr", r.RemoteAddr)) + + authorization := r.Header.Get("Authorization") + if authorization == "" { + return nil, errors.WithStack(auth.ErrUnauthenticated) + } + + rawToken := strings.TrimPrefix(authorization, "Bearer ") + if rawToken == "" { + return nil, errors.WithStack(auth.ErrUnauthenticated) + } + + token, err := parseToken(ctx, a.keys, a.issuer, rawToken) + if err != nil { + return nil, errors.WithStack(err) + } + + rawRole, exists := token.Get(keyRole) + if !exists { + return nil, errors.New("could not find 'thumbprint' claim") + } + + role, ok := rawRole.(string) + if !ok { + return nil, errors.Errorf("unexpected '%s' claim value: '%v'", keyRole, rawRole) + } + + if !isValidRole(role) { + return nil, errors.Errorf("invalid role '%s'", role) + } + + user := &User{ + subject: token.Subject(), + role: Role(role), + } + + return user, nil +} + +func NewAuthenticator(keys jwk.Set, issuer string) *Authenticator { + return &Authenticator{ + keys: keys, + issuer: issuer, + } +} + +var _ auth.Authenticator = &Authenticator{} diff --git a/internal/auth/user/jwt.go b/internal/auth/user/jwt.go new file mode 100644 index 0000000..b2d8c4e --- /dev/null +++ b/internal/auth/user/jwt.go @@ -0,0 +1,61 @@ +package user + +import ( + "context" + "time" + + "forge.cadoles.com/Cadoles/emissary/internal/jwk" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jws" + "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/pkg/errors" +) + +const keyRole = "role" + +func parseToken(ctx context.Context, keys jwk.Set, issuer string, rawToken string) (jwt.Token, error) { + token, err := jwt.Parse( + []byte(rawToken), + jwt.WithKeySet(keys, jws.WithRequireKid(false)), + jwt.WithIssuer(issuer), + jwt.WithValidate(true), + ) + if err != nil { + return nil, errors.WithStack(err) + } + + return token, nil +} + +func GenerateToken(ctx context.Context, key jwk.Key, issuer, subject string, role Role) (string, error) { + token := jwt.New() + + if err := token.Set(jwt.SubjectKey, subject); err != nil { + return "", errors.WithStack(err) + } + + if err := token.Set(jwt.IssuerKey, issuer); err != nil { + return "", errors.WithStack(err) + } + + if err := token.Set(keyRole, role); err != nil { + return "", errors.WithStack(err) + } + + now := time.Now() + + if err := token.Set(jwt.NotBeforeKey, now); err != nil { + return "", errors.WithStack(err) + } + + if err := token.Set(jwt.IssuedAtKey, now); err != nil { + return "", errors.WithStack(err) + } + + rawToken, err := jwt.Sign(token, jwt.WithKey(jwa.RS256, key)) + if err != nil { + return "", errors.WithStack(err) + } + + return string(rawToken), nil +} diff --git a/internal/auth/user/user.go b/internal/auth/user/user.go new file mode 100644 index 0000000..c055918 --- /dev/null +++ b/internal/auth/user/user.go @@ -0,0 +1,32 @@ +package user + +import "forge.cadoles.com/Cadoles/emissary/internal/auth" + +type Role string + +const ( + RoleWriter Role = "writer" + RoleReader Role = "reader" +) + +func isValidRole(r string) bool { + rr := Role(r) + + return rr == RoleWriter || rr == RoleReader +} + +type User struct { + subject string + role Role +} + +// Subject implements auth.User +func (u *User) Subject() string { + return u.subject +} + +func (u *User) Role() Role { + return u.role +} + +var _ auth.User = &User{} diff --git a/internal/client/client.go b/internal/client/client.go index ab20ec4..8c8d2fb 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -13,44 +13,49 @@ import ( ) type Client struct { - http *http.Client - token string - serverURL string + http *http.Client + defaultOpts Options + serverURL string } -func (c *Client) apiGet(ctx context.Context, path string, result any) error { - if err := c.apiDo(ctx, http.MethodGet, path, nil, result); err != nil { +func (c *Client) apiGet(ctx context.Context, path string, result any, funcs ...OptionFunc) error { + if err := c.apiDo(ctx, http.MethodGet, path, nil, result, funcs...); err != nil { return errors.WithStack(err) } return nil } -func (c *Client) apiPost(ctx context.Context, path string, payload any, result any) error { - if err := c.apiDo(ctx, http.MethodPost, path, payload, result); err != nil { +func (c *Client) apiPost(ctx context.Context, path string, payload any, result any, funcs ...OptionFunc) error { + if err := c.apiDo(ctx, http.MethodPost, path, payload, result, funcs...); err != nil { return errors.WithStack(err) } return nil } -func (c *Client) apiPut(ctx context.Context, path string, payload any, result any) error { - if err := c.apiDo(ctx, http.MethodPut, path, payload, result); err != nil { +func (c *Client) apiPut(ctx context.Context, path string, payload any, result any, funcs ...OptionFunc) error { + if err := c.apiDo(ctx, http.MethodPut, path, payload, result, funcs...); err != nil { return errors.WithStack(err) } return nil } -func (c *Client) apiDelete(ctx context.Context, path string, payload any, result any) error { - if err := c.apiDo(ctx, http.MethodDelete, path, payload, result); err != nil { +func (c *Client) apiDelete(ctx context.Context, path string, payload any, result any, funcs ...OptionFunc) error { + if err := c.apiDo(ctx, http.MethodDelete, path, payload, result, funcs...); err != nil { return errors.WithStack(err) } return nil } -func (c *Client) apiDo(ctx context.Context, method string, path string, payload any, response any) error { +func (c *Client) apiDo(ctx context.Context, method string, path string, payload any, response any, funcs ...OptionFunc) error { + opts := c.defaultOptions() + for _, fn := range funcs { + fn(opts) + } + url := c.serverURL + path logger.Debug( @@ -73,6 +78,12 @@ func (c *Client) apiDo(ctx context.Context, method string, path string, payload return errors.WithStack(err) } + for key, values := range opts.Headers { + for _, v := range values { + req.Header.Add(key, v) + } + } + res, err := c.http.Do(req) if err != nil { return errors.WithStack(err) @@ -89,6 +100,12 @@ func (c *Client) apiDo(ctx context.Context, method string, path string, payload return nil } +func (c *Client) defaultOptions() *Options { + return &Options{ + Headers: c.defaultOpts.Headers, + } +} + func withResponse[T any]() struct { Data T Error *api.Error @@ -113,9 +130,15 @@ func joinSlice[T any](items []T) string { return str } -func New(serverURL string) *Client { +func New(serverURL string, funcs ...OptionFunc) *Client { + opts := Options{} + for _, fn := range funcs { + fn(&opts) + } + return &Client{ - serverURL: serverURL, - http: &http.Client{}, + serverURL: serverURL, + http: &http.Client{}, + defaultOpts: opts, } } diff --git a/internal/client/get_agent.go b/internal/client/get_agent.go index 8e2f4e4..d95ec36 100644 --- a/internal/client/get_agent.go +++ b/internal/client/get_agent.go @@ -8,14 +8,14 @@ import ( "github.com/pkg/errors" ) -func (c *Client) GetAgent(ctx context.Context, agentID datastore.AgentID) (*datastore.Agent, error) { +func (c *Client) GetAgent(ctx context.Context, agentID datastore.AgentID, funcs ...OptionFunc) (*datastore.Agent, error) { response := withResponse[struct { Agent *datastore.Agent `json:"agent"` }]() path := fmt.Sprintf("/api/v1/agents/%d", agentID) - if err := c.apiGet(ctx, path, &response); err != nil { + if err := c.apiGet(ctx, path, &response, funcs...); err != nil { return nil, errors.WithStack(err) } diff --git a/internal/client/get_agent_specs.go b/internal/client/get_agent_specs.go index 87614bb..8f36e20 100644 --- a/internal/client/get_agent_specs.go +++ b/internal/client/get_agent_specs.go @@ -9,14 +9,14 @@ import ( "github.com/pkg/errors" ) -func (c *Client) GetAgentSpecs(ctx context.Context, agentID datastore.AgentID) ([]spec.Spec, error) { +func (c *Client) GetAgentSpecs(ctx context.Context, agentID datastore.AgentID, funcs ...OptionFunc) ([]spec.Spec, error) { response := withResponse[struct { Specs []*spec.RawSpec `json:"specs"` }]() path := fmt.Sprintf("/api/v1/agents/%d/specs", agentID) - if err := c.apiGet(ctx, path, &response); err != nil { + if err := c.apiGet(ctx, path, &response, funcs...); err != nil { return nil, errors.WithStack(err) } diff --git a/internal/client/options.go b/internal/client/options.go new file mode 100644 index 0000000..85fd12b --- /dev/null +++ b/internal/client/options.go @@ -0,0 +1,24 @@ +package client + +import "net/http" + +type Options struct { + Headers http.Header +} + +type OptionFunc func(*Options) + +func WithToken(token string) OptionFunc { + return func(o *Options) { + if o.Headers == nil { + o.Headers = http.Header{} + } + o.Headers.Set("Authorization", "Bearer "+token) + } +} + +func WithHeaders(headers http.Header) OptionFunc { + return func(o *Options) { + o.Headers = headers + } +} diff --git a/internal/client/query_agents.go b/internal/client/query_agents.go index ab8d408..8a458d2 100644 --- a/internal/client/query_agents.go +++ b/internal/client/query_agents.go @@ -12,6 +12,7 @@ import ( type QueryAgentsOptionFunc func(*QueryAgentsOptions) type QueryAgentsOptions struct { + Options []OptionFunc Limit *int Offset *int Thumbprints []string @@ -19,6 +20,12 @@ type QueryAgentsOptions struct { Statuses []datastore.AgentStatus } +func WithQueryAgentsOptions(funcs ...OptionFunc) QueryAgentsOptionFunc { + return func(opts *QueryAgentsOptions) { + opts.Options = funcs + } +} + func WithQueryAgentsLimit(limit int) QueryAgentsOptionFunc { return func(opts *QueryAgentsOptions) { opts.Limit = &limit @@ -76,7 +83,11 @@ func (c *Client) QueryAgents(ctx context.Context, funcs ...QueryAgentsOptionFunc Total int `json:"total"` }]() - if err := c.apiGet(ctx, path, &response); err != nil { + if options.Options == nil { + options.Options = make([]OptionFunc, 0) + } + + if err := c.apiGet(ctx, path, &response, options.Options...); err != nil { return nil, 0, errors.WithStack(err) } diff --git a/internal/client/register_agent.go b/internal/client/register_agent.go index b87258b..a79760a 100644 --- a/internal/client/register_agent.go +++ b/internal/client/register_agent.go @@ -9,7 +9,7 @@ import ( "github.com/pkg/errors" ) -func (c *Client) RegisterAgent(ctx context.Context, key jwk.Key, thumbprint string, meta []metadata.Tuple) (*datastore.Agent, error) { +func (c *Client) RegisterAgent(ctx context.Context, key jwk.Key, thumbprint string, meta []metadata.Tuple, funcs ...OptionFunc) (*datastore.Agent, error) { keySet, err := jwk.PublicKeySet(key) if err != nil { return nil, errors.WithStack(err) @@ -36,7 +36,7 @@ func (c *Client) RegisterAgent(ctx context.Context, key jwk.Key, thumbprint stri Agent *datastore.Agent `json:"agent"` }]() - if err := c.apiPost(ctx, "/api/v1/register", payload, &response); err != nil { + if err := c.apiPost(ctx, "/api/v1/register", payload, &response, funcs...); err != nil { return nil, errors.WithStack(err) } diff --git a/internal/client/update_agent.go b/internal/client/update_agent.go index 69bb3c5..118efce 100644 --- a/internal/client/update_agent.go +++ b/internal/client/update_agent.go @@ -9,7 +9,8 @@ import ( ) type UpdateAgentOptions struct { - Status *int + Status *int + Options []OptionFunc } type UpdateAgentOptionFunc func(*UpdateAgentOptions) @@ -20,6 +21,12 @@ func WithAgentStatus(status int) UpdateAgentOptionFunc { } } +func WithUpdateAgentsOptions(funcs ...OptionFunc) UpdateAgentOptionFunc { + return func(opts *UpdateAgentOptions) { + opts.Options = funcs + } +} + func (c *Client) UpdateAgent(ctx context.Context, agentID datastore.AgentID, funcs ...UpdateAgentOptionFunc) (*datastore.Agent, error) { opts := &UpdateAgentOptions{} for _, fn := range funcs { @@ -38,7 +45,11 @@ func (c *Client) UpdateAgent(ctx context.Context, agentID datastore.AgentID, fun path := fmt.Sprintf("/api/v1/agents/%d", agentID) - if err := c.apiPut(ctx, path, payload, &response); err != nil { + if opts.Options == nil { + opts.Options = make([]OptionFunc, 0) + } + + if err := c.apiPut(ctx, path, payload, &response, opts.Options...); err != nil { return nil, errors.WithStack(err) } diff --git a/internal/client/update_agent_spec.go b/internal/client/update_agent_spec.go index 65e39d1..36fce68 100644 --- a/internal/client/update_agent_spec.go +++ b/internal/client/update_agent_spec.go @@ -10,7 +10,7 @@ import ( "github.com/pkg/errors" ) -func (c *Client) UpdateAgentSpec(ctx context.Context, agentID datastore.AgentID, spc spec.Spec) (*datastore.Spec, error) { +func (c *Client) UpdateAgentSpec(ctx context.Context, agentID datastore.AgentID, spc spec.Spec, funcs ...OptionFunc) (*datastore.Spec, error) { payload := struct { Name spec.Name `json:"name"` Revision int `json:"revision"` @@ -27,7 +27,7 @@ func (c *Client) UpdateAgentSpec(ctx context.Context, agentID datastore.AgentID, path := fmt.Sprintf("/api/v1/agents/%d/specs", agentID) - if err := c.apiPost(ctx, path, payload, &response); err != nil { + if err := c.apiPost(ctx, path, payload, &response, funcs...); err != nil { return nil, errors.WithStack(err) } diff --git a/internal/command/agent/run.go b/internal/command/agent/run.go index 19c3e38..c01edc4 100644 --- a/internal/command/agent/run.go +++ b/internal/command/agent/run.go @@ -17,7 +17,6 @@ import ( "forge.cadoles.com/Cadoles/emissary/internal/jwk" "forge.cadoles.com/Cadoles/emissary/internal/machineid" "github.com/pkg/errors" - _ "github.com/santhosh-tekuri/jsonschema/v5/httploader" "github.com/urfave/cli/v2" "gitlab.com/wpetit/goweb/logger" ) diff --git a/internal/command/client/agent/get.go b/internal/command/client/agent/get.go index 10b26a9..5c02934 100644 --- a/internal/command/client/agent/get.go +++ b/internal/command/client/agent/get.go @@ -20,12 +20,17 @@ func GetCommand() *cli.Command { Action: func(ctx *cli.Context) error { baseFlags := clientFlag.GetBaseFlags(ctx) + token, err := clientFlag.GetToken(baseFlags) + if err != nil { + return errors.WithStack(apierr.Wrap(err)) + } + agentID, err := agentFlag.AssertAgentID(ctx) if err != nil { return errors.WithStack(err) } - client := client.New(baseFlags.ServerURL) + client := client.New(baseFlags.ServerURL, client.WithToken(token)) agent, err := client.GetAgent(ctx.Context, agentID) if err != nil { diff --git a/internal/command/client/agent/query.go b/internal/command/client/agent/query.go index 1855e66..da02625 100644 --- a/internal/command/client/agent/query.go +++ b/internal/command/client/agent/query.go @@ -18,7 +18,13 @@ func QueryCommand() *cli.Command { Flags: clientFlag.ComposeFlags(), Action: func(ctx *cli.Context) error { baseFlags := clientFlag.GetBaseFlags(ctx) - client := client.New(baseFlags.ServerURL) + + token, err := clientFlag.GetToken(baseFlags) + if err != nil { + return errors.WithStack(apierr.Wrap(err)) + } + + client := client.New(baseFlags.ServerURL, client.WithToken(token)) agents, _, err := client.QueryAgents(ctx.Context) if err != nil { diff --git a/internal/command/client/agent/update.go b/internal/command/client/agent/update.go index f2fe80f..0083d92 100644 --- a/internal/command/client/agent/update.go +++ b/internal/command/client/agent/update.go @@ -26,6 +26,11 @@ func UpdateCommand() *cli.Command { Action: func(ctx *cli.Context) error { baseFlags := clientFlag.GetBaseFlags(ctx) + token, err := clientFlag.GetToken(baseFlags) + if err != nil { + return errors.WithStack(apierr.Wrap(err)) + } + agentID, err := agentFlag.AssertAgentID(ctx) if err != nil { return errors.WithStack(err) @@ -38,7 +43,7 @@ func UpdateCommand() *cli.Command { options = append(options, client.WithAgentStatus(status)) } - client := client.New(baseFlags.ServerURL) + client := client.New(baseFlags.ServerURL, client.WithToken(token)) agent, err := client.UpdateAgent(ctx.Context, agentID, options...) if err != nil { diff --git a/internal/command/client/flag/flag.go b/internal/command/client/flag/flag.go index f9bc8af..04771bd 100644 --- a/internal/command/client/flag/flag.go +++ b/internal/command/client/flag/flag.go @@ -2,9 +2,13 @@ package flag import ( "fmt" + "io/ioutil" + "os" + "strings" "forge.cadoles.com/Cadoles/emissary/internal/format" "forge.cadoles.com/Cadoles/emissary/internal/format/table" + "github.com/pkg/errors" "github.com/urfave/cli/v2" ) @@ -28,6 +32,17 @@ func ComposeFlags(flags ...cli.Flag) []cli.Flag { Usage: fmt.Sprintf("use `MODE` as output mode (available: %s)", []format.OutputMode{format.OutputModeCompact, format.OutputModeWide}), Value: string(format.OutputModeCompact), }, + &cli.StringFlag{ + Name: "token", + Aliases: []string{"t"}, + Usage: "use `TOKEN` as authentification token", + }, + &cli.StringFlag{ + Name: "token-file", + Usage: "use `TOKEN_FILE` as file containing the authentification token", + Value: ".emissary-token", + TakesFile: true, + }, } flags = append(flags, baseFlags...) @@ -39,16 +54,43 @@ type BaseFlags struct { ServerURL string Format format.Format OutputMode format.OutputMode + Token string + TokenFile string } func GetBaseFlags(ctx *cli.Context) *BaseFlags { serverURL := ctx.String("server") rawFormat := ctx.String("format") rawOutputMode := ctx.String("output-mode") + tokenFile := ctx.String("token-file") + token := ctx.String("token") return &BaseFlags{ ServerURL: serverURL, Format: format.Format(rawFormat), OutputMode: format.OutputMode(rawOutputMode), + Token: token, + TokenFile: tokenFile, } } + +func GetToken(flags *BaseFlags) (string, error) { + if flags.Token != "" { + return flags.Token, nil + } + + if flags.TokenFile == "" { + return "", nil + } + + rawToken, err := ioutil.ReadFile(flags.TokenFile) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return "", errors.WithStack(err) + } + + if rawToken == nil { + return "", nil + } + + return strings.TrimSpace(string(rawToken)), nil +} diff --git a/internal/command/server/auth/create_token.go b/internal/command/server/auth/create_token.go new file mode 100644 index 0000000..ae22d5b --- /dev/null +++ b/internal/command/server/auth/create_token.go @@ -0,0 +1,54 @@ +package auth + +import ( + "fmt" + + "forge.cadoles.com/Cadoles/emissary/internal/auth/user" + "forge.cadoles.com/Cadoles/emissary/internal/command/common" + "forge.cadoles.com/Cadoles/emissary/internal/jwk" + "github.com/lithammer/shortuuid/v4" + "github.com/pkg/errors" + "github.com/urfave/cli/v2" +) + +func CreateTokenCommand() *cli.Command { + return &cli.Command{ + Name: "create-token", + Usage: "Create a new authentification token", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "role", + Usage: fmt.Sprintf("associate `ROLE` to the token (available: %v)", []user.Role{user.RoleReader, user.RoleWriter}), + Value: string(user.RoleReader), + }, + &cli.StringFlag{ + Name: "subject", + Usage: "associate `SUBJECT` to the token", + Value: fmt.Sprintf("user-%s", shortuuid.New()), + }, + }, + Action: func(ctx *cli.Context) error { + conf, err := common.LoadConfig(ctx) + if err != nil { + return errors.Wrap(err, "Could not load configuration") + } + + subject := ctx.String("subject") + role := ctx.String("role") + + key, err := jwk.LoadOrGenerate(string(conf.Server.PrivateKeyPath), jwk.DefaultKeySize) + if err != nil { + return errors.WithStack(err) + } + + token, err := user.GenerateToken(ctx.Context, key, string(conf.Server.Issuer), subject, user.Role(role)) + if err != nil { + return errors.WithStack(err) + } + + fmt.Println(token) + + return nil + }, + } +} diff --git a/internal/command/server/auth/root.go b/internal/command/server/auth/root.go new file mode 100644 index 0000000..ccb554a --- /dev/null +++ b/internal/command/server/auth/root.go @@ -0,0 +1,15 @@ +package auth + +import ( + "github.com/urfave/cli/v2" +) + +func Root() *cli.Command { + return &cli.Command{ + Name: "auth", + Usage: "Authentication related commands", + Subcommands: []*cli.Command{ + CreateTokenCommand(), + }, + } +} diff --git a/internal/command/server/database/migrate.go b/internal/command/server/database/migrate.go index adff515..ad9b51e 100644 --- a/internal/command/server/database/migrate.go +++ b/internal/command/server/database/migrate.go @@ -36,8 +36,8 @@ func MigrateCommand() *cli.Command { return errors.Wrap(err, "Could not load configuration") } - driver := string(conf.Database.Driver) - dsn := string(conf.Database.DSN) + driver := string(conf.Server.Database.Driver) + dsn := string(conf.Server.Database.DSN) migr, err := migrate.New("migrations", driver, dsn) if err != nil { diff --git a/internal/command/server/database/ping.go b/internal/command/server/database/ping.go index bfb8760..683d700 100644 --- a/internal/command/server/database/ping.go +++ b/internal/command/server/database/ping.go @@ -20,10 +20,10 @@ func PingCommand() *cli.Command { return errors.Wrap(err, "Could not load configuration") } - logger.Info(ctx.Context, "connecting to database", logger.F("dsn", conf.Database.DSN)) + logger.Info(ctx.Context, "connecting to database", logger.F("dsn", conf.Server.Database.DSN)) - driver := string(conf.Database.Driver) - dsn := string(conf.Database.DSN) + driver := string(conf.Server.Database.Driver) + dsn := string(conf.Server.Database.DSN) db, err := sql.Open(driver, dsn) if err != nil { @@ -40,7 +40,7 @@ func PingCommand() *cli.Command { return errors.WithStack(err) } - logger.Info(ctx.Context, "connection succeeded", logger.F("dsn", conf.Database.DSN)) + logger.Info(ctx.Context, "connection succeeded", logger.F("dsn", conf.Server.Database.DSN)) return nil }, diff --git a/internal/command/server/database/reset.go b/internal/command/server/database/reset.go index eebf804..0ba52b5 100644 --- a/internal/command/server/database/reset.go +++ b/internal/command/server/database/reset.go @@ -18,8 +18,8 @@ func ResetCommand() *cli.Command { return errors.Wrap(err, "Could not load configuration") } - driver := string(conf.Database.Driver) - dsn := string(conf.Database.DSN) + driver := string(conf.Server.Database.Driver) + dsn := string(conf.Server.Database.DSN) migr, err := migrate.New("migrations", driver, dsn) if err != nil { diff --git a/internal/command/server/root.go b/internal/command/server/root.go index 2808b14..17e43c9 100644 --- a/internal/command/server/root.go +++ b/internal/command/server/root.go @@ -2,6 +2,7 @@ package server import ( "forge.cadoles.com/Cadoles/emissary/internal/command/config" + "forge.cadoles.com/Cadoles/emissary/internal/command/server/auth" "forge.cadoles.com/Cadoles/emissary/internal/command/server/database" "github.com/urfave/cli/v2" ) @@ -14,6 +15,7 @@ func Root() *cli.Command { RunCommand(), database.Root(), config.Root(), + auth.Root(), }, } } diff --git a/internal/command/server/run.go b/internal/command/server/run.go index 497fb91..b5513e8 100644 --- a/internal/command/server/run.go +++ b/internal/command/server/run.go @@ -29,7 +29,7 @@ func RunCommand() *cli.Command { logger.SetLevel(logger.Level(conf.Logger.Level)) srv := server.New( - server.WithConfig(conf), + server.WithConfig(conf.Server), ) addrs, srvErrs := srv.Start(ctx.Context) diff --git a/internal/config/config.go b/internal/config/config.go index bd2a96c..3f9ea7b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,11 +10,9 @@ import ( // Config definition type Config struct { - HTTP HTTPConfig `yaml:"http"` - Logger LoggerConfig `yaml:"logger"` - Database DatabaseConfig `yaml:"database"` - CORS CORSConfig `yaml:"cors"` - Agent AgentConfig `yaml:"agent"` + Logger LoggerConfig `yaml:"logger"` + Server ServerConfig `yaml:"server"` + Agent AgentConfig `yaml:"agent"` } // NewFromFile retrieves the configuration from the given file @@ -43,11 +41,9 @@ func NewDumpDefault() *Config { // NewDefault return new default configuration func NewDefault() *Config { return &Config{ - HTTP: NewDefaultHTTPConfig(), - Logger: NewDefaultLoggerConfig(), - Database: NewDefaultDatabaseConfig(), - CORS: NewDefaultCORSConfig(), - Agent: NewDefaultAgentConfig(), + Logger: NewDefaultLoggerConfig(), + Agent: NewDefaultAgentConfig(), + Server: NewDefaultServerConfig(), } } diff --git a/internal/config/server.go b/internal/config/server.go new file mode 100644 index 0000000..b196d46 --- /dev/null +++ b/internal/config/server.go @@ -0,0 +1,19 @@ +package config + +type ServerConfig struct { + PrivateKeyPath InterpolatedString `yaml:"privateKeyPath"` + Issuer InterpolatedString `yaml:"issuer"` + HTTP HTTPConfig `yaml:"http"` + Database DatabaseConfig `yaml:"database"` + CORS CORSConfig `yaml:"cors"` +} + +func NewDefaultServerConfig() ServerConfig { + return ServerConfig{ + PrivateKeyPath: "server-key.json", + Issuer: "http://127.0.0.1:3000", + HTTP: NewDefaultHTTPConfig(), + Database: NewDefaultDatabaseConfig(), + CORS: NewDefaultCORSConfig(), + } +} diff --git a/internal/server/init.go b/internal/server/init.go index 1b22ec5..b1be69f 100644 --- a/internal/server/init.go +++ b/internal/server/init.go @@ -8,7 +8,7 @@ import ( ) func (s *Server) initRepositories(ctx context.Context) error { - agentRepo, err := setup.NewAgentRepository(ctx, s.conf) + agentRepo, err := setup.NewAgentRepository(ctx, s.conf.Database) if err != nil { return errors.WithStack(err) } diff --git a/internal/server/option.go b/internal/server/option.go index 4875b62..24467b5 100644 --- a/internal/server/option.go +++ b/internal/server/option.go @@ -1,20 +1,22 @@ package server -import "forge.cadoles.com/Cadoles/emissary/internal/config" +import ( + "forge.cadoles.com/Cadoles/emissary/internal/config" +) type Option struct { - Config *config.Config + Config config.ServerConfig } type OptionFunc func(*Option) func defaultOption() *Option { return &Option{ - Config: config.NewDefault(), + Config: config.NewDefaultServerConfig(), } } -func WithConfig(conf *config.Config) OptionFunc { +func WithConfig(conf config.ServerConfig) OptionFunc { return func(opt *Option) { opt.Config = conf } diff --git a/internal/server/server.go b/internal/server/server.go index 6491c1a..31a3dd5 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -7,8 +7,12 @@ import ( "net" "net/http" + "forge.cadoles.com/Cadoles/emissary/internal/auth" + "forge.cadoles.com/Cadoles/emissary/internal/auth/agent" + "forge.cadoles.com/Cadoles/emissary/internal/auth/user" "forge.cadoles.com/Cadoles/emissary/internal/config" "forge.cadoles.com/Cadoles/emissary/internal/datastore" + "forge.cadoles.com/Cadoles/emissary/internal/jwk" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/go-chi/cors" @@ -17,7 +21,7 @@ import ( ) type Server struct { - conf *config.Config + conf config.ServerConfig agentRepo datastore.AgentRepository } @@ -68,6 +72,20 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e } }() + key, err := jwk.LoadOrGenerate(string(s.conf.PrivateKeyPath), jwk.DefaultKeySize) + if err != nil { + errs <- errors.WithStack(err) + + return + } + + keys, err := jwk.PublicKeySet(key) + if err != nil { + errs <- errors.WithStack(err) + + return + } + router := chi.NewRouter() router.Use(middleware.Logger) @@ -85,15 +103,22 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e router.Route("/api/v1", func(r chi.Router) { r.Post("/register", s.registerAgent) - r.Route("/agents", func(r chi.Router) { - r.Get("/", s.queryAgents) - r.Get("/{agentID}", s.getAgent) - r.Put("/{agentID}", s.updateAgent) - r.Delete("/{agentID}", s.deleteAgent) + r.Group(func(r chi.Router) { + r.Use(auth.Middleware( + user.NewAuthenticator(keys, string(s.conf.Issuer)), + agent.NewAuthenticator(s.agentRepo), + )) - r.Get("/{agentID}/specs", s.getAgentSpecs) - r.Post("/{agentID}/specs", s.updateSpec) - r.Delete("/{agentID}/specs", s.deleteSpec) + r.Route("/agents", func(r chi.Router) { + r.Get("/", s.queryAgents) + r.Get("/{agentID}", s.getAgent) + r.Put("/{agentID}", s.updateAgent) + r.Delete("/{agentID}", s.deleteAgent) + + r.Get("/{agentID}/specs", s.getAgentSpecs) + r.Post("/{agentID}/specs", s.updateSpec) + r.Delete("/{agentID}/specs", s.deleteSpec) + }) }) }) diff --git a/internal/setup/repository.go b/internal/setup/repository.go index 1874e48..da824b2 100644 --- a/internal/setup/repository.go +++ b/internal/setup/repository.go @@ -31,9 +31,9 @@ func openPostgresPool(ctx context.Context, dsn string) (*pgxpool.Pool, error) { return postgresPool, nil } -func NewAgentRepository(ctx context.Context, conf *config.Config) (datastore.AgentRepository, error) { - driver := string(conf.Database.Driver) - dsn := string(conf.Database.DSN) +func NewAgentRepository(ctx context.Context, conf config.DatabaseConfig) (datastore.AgentRepository, error) { + driver := string(conf.Driver) + dsn := string(conf.DSN) var agentRepository datastore.AgentRepository diff --git a/modd.conf b/modd.conf index bd284b1..ab5cbcc 100644 --- a/modd.conf +++ b/modd.conf @@ -6,7 +6,8 @@ tmp/config.yml prep: make build-emissary prep: make tmp/server.yml prep: make tmp/agent.yml - prep: make run-emissary-server EMISSARY_CMD="--debug --config tmp/agent.yml server database migrate" + prep: make run-emissary-server EMISSARY_CMD="--debug --config tmp/server.yml server database migrate" + prep: make .emissary-token daemon: make run-emissary-server EMISSARY_CMD="--debug --config tmp/server.yml server run" daemon: make run-emissary-agent EMISSARY_CMD="--debug --config tmp/agent.yml agent run" } \ No newline at end of file