diff --git a/authmw/token.go b/authmw/token.go index 8f6f9db..492b3f5 100644 --- a/authmw/token.go +++ b/authmw/token.go @@ -75,6 +75,7 @@ func VerifyToken(authURL string, permittedRoles []string) func(http.Handler) htt _ = encoder.Encode(&errResp) return } + // TODO: Check that it is in permitted // Add claims to request context if claims, ok := token.Claims.(*auth.MicrofilmClaims); ok && token.Valid { diff --git a/go.mod b/go.mod index 983d08e..ef94242 100644 --- a/go.mod +++ b/go.mod @@ -10,15 +10,35 @@ require ( github.com/nats-io/nats.go v1.31.0 github.com/pelletier/go-toml/v2 v2.1.0 github.com/urfave/cli/v2 v2.25.7 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 + go.opentelemetry.io/otel v1.19.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 + go.opentelemetry.io/otel/sdk v1.19.0 + go.opentelemetry.io/otel/trace v1.19.0 ) require ( + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/klauspost/compress v1.17.0 // indirect github.com/nats-io/nkeys v0.4.5 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect ) diff --git a/go.sum b/go.sum index 12823a3..933cb4e 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,35 @@ git.t-juice.club/microfilm/users v0.1.2 h1:wudwa4C5ecUGmbe+Y6A77lVHx8dFSy/ib47HBOrQ7AU= git.t-juice.club/microfilm/users v0.1.2/go.mod h1:CWb2XYyifeaiLMdEqPyLB4EEj2MKcGogt+wt+PGdcSw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= @@ -36,10 +55,43 @@ github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/server/middleware.go b/server/middleware.go index bc6e415..8595b6f 100644 --- a/server/middleware.go +++ b/server/middleware.go @@ -1,10 +1,13 @@ package server import ( + "fmt" "net/http" "time" "github.com/go-chi/chi/v5/middleware" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/trace" ) func (s *Server) MiddlewareLogging(next http.Handler) http.Handler { @@ -28,3 +31,14 @@ func (s *Server) MiddlewareLogging(next http.Handler) http.Handler { } return http.HandlerFunc(fn) } + +func (s *Server) MiddlewareTracing(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + span := trace.SpanFromContext(r.Context()) + span.AddEvent("event") + + h := otelhttp.NewHandler(next, fmt.Sprintf("%s %s", r.Method, r.URL.Path)) + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} diff --git a/server/server.go b/server/server.go index 5c84181..7b4ae70 100644 --- a/server/server.go +++ b/server/server.go @@ -1,6 +1,7 @@ package server import ( + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -19,6 +20,13 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "github.com/nats-io/nats.go" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.opentelemetry.io/otel/trace" ) const DefaultTokenDuration time.Duration = 24 * time.Hour @@ -38,10 +46,17 @@ type Server struct { func NewServer(config *Config) (*Server, error) { srv := &Server{} + tp, err := tracerProvider("jaeger:4318") + if err != nil { + return nil, err + } + otel.SetTracerProvider(tp) + r := chi.NewRouter() r.Use(middleware.RequestID) r.Use(srv.MiddlewareLogging) + r.Use(srv.MiddlewareTracing) r.Get("/key", srv.PubkeyHandler) r.Post("/{id}/token", srv.TokenHandler) @@ -79,6 +94,26 @@ func NewServer(config *Config) (*Server, error) { return srv, nil } +func tracerProvider(url string) (*sdktrace.TracerProvider, error) { + exp, err := otlptracehttp.New(context.Background(), otlptracehttp.WithEndpoint(url), otlptracehttp.WithInsecure()) + if err != nil { + return nil, err + } + + res := resource.NewWithAttributes(semconv.SchemaURL, + semconv.ServiceName("mf-auth"), + semconv.ServiceVersion(auth.Version), + ) + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exp, sdktrace.WithBatchTimeout(time.Second)), + sdktrace.WithResource(res), + ) + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) + + return tp, nil +} + func InfoHandler(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) @@ -96,6 +131,9 @@ func WriteError(w http.ResponseWriter, response auth.ErrorResponse) { } func (s *Server) PubkeyHandler(w http.ResponseWriter, r *http.Request) { + span := trace.SpanFromContext(r.Context()) + + span.AddEvent("Start marshalling public key.") enc := json.NewEncoder(w) key, err := x509.MarshalPKIXPublicKey(s.signingKey.Public()) if err != nil { @@ -106,6 +144,7 @@ func (s *Server) PubkeyHandler(w http.ResponseWriter, r *http.Request) { }) return } + span.AddEvent("Finished marshalling public key.") response := auth.PubkeyResponse{ PubKey: key, } @@ -114,6 +153,7 @@ func (s *Server) PubkeyHandler(w http.ResponseWriter, r *http.Request) { } func (s *Server) TokenHandler(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() decoder := json.NewDecoder(r.Body) defer r.Body.Close() @@ -135,7 +175,7 @@ func (s *Server) TokenHandler(w http.ResponseWriter, r *http.Request) { return } - if err := s.userClient.VerifyUserPassword(userIdentifier, request.Password); err != nil { + if err := s.userClient.VerifyUserPassword(ctx, userIdentifier, request.Password); err != nil { WriteError(w, auth.ErrorResponse{ Status: http.StatusUnauthorized, Message: fmt.Sprintf("Unable to verify password: %s", err), @@ -143,7 +183,7 @@ func (s *Server) TokenHandler(w http.ResponseWriter, r *http.Request) { return } - u, err := s.userClient.GetUser(userIdentifier) + u, err := s.userClient.GetUser(ctx, userIdentifier) if err != nil { WriteError(w, auth.ErrorResponse{ Status: http.StatusUnauthorized, diff --git a/server/userclient.go b/server/userclient.go index 7b0358a..1694825 100644 --- a/server/userclient.go +++ b/server/userclient.go @@ -9,20 +9,31 @@ import ( "time" "git.t-juice.club/microfilm/users" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" ) type UserClient struct { BaseURL string + client *http.Client } const defaultTimeout time.Duration = 5 * time.Second func NewUserClient(baseurl string) *UserClient { - return &UserClient{BaseURL: baseurl} + return &UserClient{ + BaseURL: baseurl, + client: &http.Client{ + Transport: otelhttp.NewTransport(http.DefaultTransport), + }, + } } -func (c *UserClient) VerifyUserPassword(username, password string) error { - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) +func (c *UserClient) VerifyUserPassword(ctx context.Context, username, password string) error { + ctx, span := otel.GetTracerProvider().Tracer("").Start(ctx, "verify-user-password") + defer span.End() + + ctx, cancel := context.WithTimeout(ctx, defaultTimeout) defer cancel() url := fmt.Sprintf("%s/%s/verify", c.BaseURL, username) @@ -42,12 +53,11 @@ func (c *UserClient) VerifyUserPassword(username, password string) error { return err } - client := http.Client{} - - resp, err := client.Do(req) + resp, err := c.client.Do(req) if err != nil { return err } + defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("authentication failed") @@ -56,10 +66,13 @@ func (c *UserClient) VerifyUserPassword(username, password string) error { return nil } -func (c *UserClient) GetUser(identifier string) (users.User, error) { +func (c *UserClient) GetUser(ctx context.Context, identifier string) (users.User, error) { var u users.User - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + ctx, span := otel.GetTracerProvider().Tracer("").Start(ctx, "get-user") + defer span.End() + + ctx, cancel := context.WithTimeout(ctx, defaultTimeout) defer cancel() url := fmt.Sprintf("%s/%s", c.BaseURL, identifier) @@ -69,9 +82,7 @@ func (c *UserClient) GetUser(identifier string) (users.User, error) { return u, err } - client := http.Client{} - - resp, err := client.Do(req) + resp, err := c.client.Do(req) if err != nil { return u, err } diff --git a/version.go b/version.go index 9811450..2a64124 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package auth -const Version = "v0.1.1" +const Version = "v0.1.2"