diff --git a/.github/workflows/ochami.yml b/.github/workflows/ochami.yml index 0118bca..1c248bd 100644 --- a/.github/workflows/ochami.yml +++ b/.github/workflows/ochami.yml @@ -5,7 +5,7 @@ on: push: tags: - v* - + permissions: write-all # Necessary for the generate-build-provenance action with containers jobs: @@ -52,11 +52,11 @@ jobs: node process.js echo "digest=$(cat digest.txt)" >> $GITHUB_OUTPUT - name: Attest Binaries - uses: github-early-access/generate-build-provenance@main + uses: actions/attest-build-provenance@v1 with: - subject-path: dist/smd* + subject-path: dist/cloud-init* - name: generate build provenance - uses: github-early-access/generate-build-provenance@main + uses: actions/attest-build-provenance@v1 with: subject-name: ghcr.io/openchami/cloud-init subject-digest: ${{ steps.process_goreleaser_output.outputs.digest }} diff --git a/.gitignore b/.gitignore index 53c37a1..1942676 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -dist \ No newline at end of file +dist +# Our compiled binary +/cloud-init-server diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b115cbb..4dd451f 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -24,8 +24,8 @@ dockers: image_templates: - ghcr.io/openchami/{{.ProjectName}}:latest - ghcr.io/openchami/{{.ProjectName}}:{{ .Tag }} - - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }} - - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}.{{ .Minor }} + - ghcr.io/openchami/{{.ProjectName}}:v{{ .Major }} + - ghcr.io/openchami/{{.ProjectName}}:v{{ .Major }}.{{ .Minor }} build_flag_templates: - "--pull" - "--label=org.opencontainers.image.created={{.Date}}" diff --git a/CHANGELOG.md b/CHANGELOG.md index d964b07..edd8a90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.0] - 2024-07-17 + +### Added + +- Added an additional URL endpoint (`/cloud-init-secure`) which requires JWT authentication for access + - At the Docker level, if the `JWKS_URL` env var is set, this server will attempt to load the corresponding JSON Web Key Set at startup. + If this succeeds, the secure route will be enabled, with tokens validated against the JWKS keyset. +- During a query, if no xnames are found for the given input name (usually a MAC address), the input name is used directly. + This enables users to query an xname (i.e. without needing to look up its MAC first and query using that), or a group name. + +### Changed + +- Switched from [Gin](https://github.com/gin-gonic/gin) HTTP router to [Chi](https://github.com/go-chi/chi) +- When adding entries to the internal datastore, names are no longer "slug-ified" (via the `gosimple/slug` package). + This means that when a user requests data for a node, the name they query should be a standard colon-separated MAC address, as opposed to using dashes. +- Rather than requiring a single static JWT on launch, we now accept an OIDC token endpoint. New JWTs are requested from the endpoint as necessary, allowing us to run for longer than the lifetime of a single token. + ## [0.0.4] - 2024-01-17 ### Added @@ -13,4 +30,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Created SMD client - Added memory-based store - Able to provide cloud-init payloads that work with newly booted nodes -- Build and release with goreleaser \ No newline at end of file +- Build and release with goreleaser diff --git a/Dockerfile b/Dockerfile index a442d6f..16d424c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,15 +35,16 @@ RUN set -ex \ # Get the boot-script-service from the builder stage. COPY cloud-init-server /usr/local/bin/ +ENV TOKEN_URL="http://opaal:3333/token" ENV SMD_URL="http://smd:27779" -ENV SMD_TOKEN="" ENV LISTEN_ADDR="0.0.0.0:27777" +ENV JWKS_URL="" # nobody 65534:65534 USER 65534:65534 # Set up the command to start the service. -CMD /usr/local/bin/cloud-init-server --listen ${LISTEN_ADDR} --smd-url ${SMD_URL} --smd-token ${SMD_TOKEN} +CMD /usr/local/bin/cloud-init-server --listen ${LISTEN_ADDR} --smd-url ${SMD_URL} --token-url ${TOKEN_URL} --jwks-url ${JWKS_URL:-""} ENTRYPOINT ["/sbin/tini", "--"] diff --git a/cmd/cloud-init-server/auth.go b/cmd/cloud-init-server/auth.go new file mode 100644 index 0000000..5a52dfc --- /dev/null +++ b/cmd/cloud-init-server/auth.go @@ -0,0 +1,61 @@ +package main + +// Adapted from OpenCHAMI SMD's auth.go + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + jwtauth "github.com/OpenCHAMI/jwtauth/v5" + "github.com/lestrrat-go/jwx/v2/jwk" +) + +type statusCheckTransport struct { + http.RoundTripper +} + +func (ct *statusCheckTransport) RoundTrip(req *http.Request) (*http.Response, error) { + resp, err := http.DefaultTransport.RoundTrip(req) + if err == nil && resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("status code: %d", resp.StatusCode) + } + + return resp, err +} + +func newHTTPClient() *http.Client { + return &http.Client{Transport: &statusCheckTransport{}} +} + +func fetchPublicKeyFromURL(url string) (*jwtauth.JWTAuth, error) { + client := newHTTPClient() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + set, err := jwk.Fetch(ctx, url, jwk.WithHTTPClient(client)) + if err != nil { + msg := "%w" + + // if the error tree contains an EOF, it means that the response was empty, + // so add a more descriptive message to the error tree + if errors.Is(err, io.EOF) { + msg = "received empty response for key: %w" + } + + return nil, fmt.Errorf(msg, err) + } + jwks, err := json.Marshal(set) + if err != nil { + return nil, fmt.Errorf("failed to marshal JWKS: %v", err) + } + keyset, err := jwtauth.NewKeySet(jwks) + if err != nil { + return nil, fmt.Errorf("failed to initialize JWKS: %v", err) + } + + return keyset, nil +} diff --git a/cmd/cloud-init-server/handlers.go b/cmd/cloud-init-server/handlers.go index 961fba2..e188939 100644 --- a/cmd/cloud-init-server/handlers.go +++ b/cmd/cloud-init-server/handlers.go @@ -1,14 +1,16 @@ package main import ( + "encoding/json" "fmt" + "io" "net/http" "github.com/OpenCHAMI/cloud-init/internal/memstore" "github.com/OpenCHAMI/cloud-init/internal/smdclient" "github.com/OpenCHAMI/cloud-init/pkg/citypes" - "github.com/gin-gonic/gin" - "github.com/gosimple/slug" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" yaml "gopkg.in/yaml.v2" ) @@ -30,13 +32,13 @@ func NewCiHandler(s ciStore, c *smdclient.SMDClient) *CiHandler { // @Produce json // @Success 200 {object} map[string]CI // @Router /harbor [get] -func (h CiHandler) ListEntries(c *gin.Context) { +func (h CiHandler) ListEntries(w http.ResponseWriter, r *http.Request) { ci, err := h.store.List() if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusInternalServerError) } - c.JSON(200, ci) + render.JSON(w, r, ci) } // AddEntry godoc @@ -49,26 +51,29 @@ func (h CiHandler) ListEntries(c *gin.Context) { // @Failure 400 {string} string "bad request" // @Failure 500 {string} string "internal server error" // @Router /harbor [post] -func (h CiHandler) AddEntry(c *gin.Context) { +func (h CiHandler) AddEntry(w http.ResponseWriter, r *http.Request) { var ci citypes.CI - if err := c.ShouldBindJSON(&ci); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if err = json.Unmarshal(body, &ci); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) return } - id := slug.Make(ci.Name) - - err := h.store.Add(id, ci) + err = h.store.Add(ci.Name, ci) if err != nil { if err == memstore.ExistingEntryErr { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusNotFound) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusInternalServerError) return } - c.JSON(http.StatusOK, ci.Name) + render.JSON(w, r, ci.Name) } // GetEntry godoc @@ -79,89 +84,102 @@ func (h CiHandler) AddEntry(c *gin.Context) { // @Success 200 {object} CI // @Failure 404 {string} string "not found" // @Router /harbor/{id} [get] -func (h CiHandler) GetEntry(c *gin.Context) { - id := c.Param("id") +func (h CiHandler) GetEntry(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") ci, err := h.store.Get(id, h.sm) if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusNotFound) + } else { + render.JSON(w, r, ci) } - - c.JSON(200, ci) } -func (h CiHandler) GetUserData(c *gin.Context) { - id := c.Param("id") +func (h CiHandler) GetUserData(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") ci, err := h.store.Get(id, h.sm) if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusNotFound) } ud, err := yaml.Marshal(ci.CIData.UserData) if err != nil { fmt.Print(err) } s := fmt.Sprintf("#cloud-config\n%s", string(ud[:])) - //c.Header("Content-Type", "text/yaml") - c.Data(200, "text/yaml", []byte(s)) + w.Header().Set("Content-Type", "text/yaml") + w.Write([]byte(s)) } -func (h CiHandler) GetMetaData(c *gin.Context) { - id := c.Param("id") +func (h CiHandler) GetMetaData(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") ci, err := h.store.Get(id, h.sm) if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusNotFound) } - - c.YAML(200, ci.CIData.MetaData) + md, err := yaml.Marshal(ci.CIData.MetaData) + if err != nil { + fmt.Print(err) + } + w.Header().Set("Content-Type", "text/yaml") + w.Write([]byte(md)) } -func (h CiHandler) GetVendorData(c *gin.Context) { - id := c.Param("id") +func (h CiHandler) GetVendorData(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") ci, err := h.store.Get(id, h.sm) if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusNotFound) } - - c.YAML(200, ci.CIData.VendorData) + md, err := yaml.Marshal(ci.CIData.VendorData) + if err != nil { + fmt.Print(err) + } + w.Header().Set("Content-Type", "text/yaml") + w.Write([]byte(md)) } -func (h CiHandler) UpdateEntry(c *gin.Context) { +func (h CiHandler) UpdateEntry(w http.ResponseWriter, r *http.Request) { var ci citypes.CI - if err := c.ShouldBindJSON(&ci); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if err = json.Unmarshal(body, &ci); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) return } - id := c.Param("id") + id := chi.URLParam(r, "id") - err := h.store.Update(id, ci) + err = h.store.Update(id, ci) if err != nil { if err == memstore.NotFoundErr { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusNotFound) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusInternalServerError) return } - c.JSON(http.StatusOK, id) + render.JSON(w, r, id) } -func (h CiHandler) DeleteEntry(c *gin.Context) { - id := c.Param("id") +func (h CiHandler) DeleteEntry(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") err := h.store.Remove(id) if err != nil { if err == memstore.NotFoundErr { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusNotFound) return } - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + http.Error(w, err.Error(), http.StatusInternalServerError) return } - c.JSON(http.StatusOK, gin.H{"status": "success"}) + render.JSON(w, r, map[string]string{"status": "success"}) } diff --git a/cmd/cloud-init-server/main.go b/cmd/cloud-init-server/main.go index b572637..ec20954 100644 --- a/cmd/cloud-init-server/main.go +++ b/cmd/cloud-init-server/main.go @@ -2,37 +2,91 @@ package main import ( "flag" + "fmt" + "net/http" + "time" "github.com/OpenCHAMI/cloud-init/internal/memstore" "github.com/OpenCHAMI/cloud-init/internal/smdclient" - "github.com/gin-gonic/gin" + "github.com/OpenCHAMI/jwtauth/v5" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" ) var ( - ciEndpoint = ":27777" - smdEndpoint = "http://smd:27779" - smdToken = "" // jwt for access to smd + ciEndpoint = ":27777" + tokenEndpoint = "http://opaal:3333/token" // jwt for smd access obtained from here + smdEndpoint = "http://smd:27779" + jwksUrl = "" // jwt keyserver URL for secure-route token validation ) func main() { flag.StringVar(&ciEndpoint, "listen", ciEndpoint, "Server IP and port for cloud-init-server to listen on") + flag.StringVar(&tokenEndpoint, "token-url", tokenEndpoint, "OIDC server URL (endpoint) to fetch new tokens from (for SMD access)") flag.StringVar(&smdEndpoint, "smd-url", smdEndpoint, "http IP/url and port for running SMD") - flag.StringVar(&smdToken, "smd-token", smdToken, "JWT token for SMD access") + flag.StringVar(&jwksUrl, "jwks-url", jwksUrl, "JWT keyserver URL, required to enable secure route") flag.Parse() - router := gin.Default() + // Set up JWT verification via the specified URL, if any + var keyset *jwtauth.JWTAuth + secureRouteEnable := false + if jwksUrl != "" { + var err error + keyset, err = fetchPublicKeyFromURL(jwksUrl) + if err != nil { + fmt.Printf("JWKS initialization failed: %s\n", err) + } else { + // JWKS init SUCCEEDED, secure route supported + secureRouteEnable = true + } + } else { + fmt.Println("No JWKS URL provided; secure route will be disabled") + } + + // Primary router and shared SMD client + router := chi.NewRouter() + router.Use( + middleware.RequestID, + middleware.RealIP, + middleware.Logger, + middleware.Recoverer, + middleware.StripSlashes, + middleware.Timeout(60 * time.Second), + ) + sm := smdclient.NewSMDClient(smdEndpoint, tokenEndpoint) + + // Unsecured datastore and router store := memstore.NewMemStore() - sm := smdclient.NewSMDClient(smdEndpoint, smdToken) ciHandler := NewCiHandler(store, sm) + router_unsec := chi.NewRouter() + initCiRouter(router_unsec, ciHandler) + router.Mount("/cloud-init", router_unsec) - router.GET("/cloud-init", ciHandler.ListEntries) - router.POST("/cloud-init", ciHandler.AddEntry) - router.GET("/cloud-init/:id", ciHandler.GetEntry) - router.GET("/cloud-init/:id/user-data", ciHandler.GetUserData) - router.GET("/cloud-init/:id/meta-data", ciHandler.GetMetaData) - router.GET("/cloud-init/:id/vendor-data", ciHandler.GetVendorData) - router.PUT("/cloud-init/:id", ciHandler.UpdateEntry) - router.DELETE("cloud-init/:id", ciHandler.DeleteEntry) + if secureRouteEnable { + // Secured datastore and router + store_sec := memstore.NewMemStore() + ciHandler_sec := NewCiHandler(store_sec, sm) + router_sec := chi.NewRouter() + router_sec.Use( + jwtauth.Verifier(keyset), + jwtauth.Authenticator(keyset), + ) + initCiRouter(router_sec, ciHandler_sec) + router.Mount("/cloud-init-secure", router_sec) + } + + // Serve all routes + http.ListenAndServe(ciEndpoint, router) +} - router.Run(ciEndpoint) +func initCiRouter(router chi.Router, handler *CiHandler) { + // Add cloud-init endpoints to router + router.Get("/", handler.ListEntries) + router.Post("/", handler.AddEntry) + router.Get("/{id}", handler.GetEntry) + router.Get("/{id}/user-data", handler.GetUserData) + router.Get("/{id}/meta-data", handler.GetMetaData) + router.Get("/{id}/vendor-data", handler.GetVendorData) + router.Put("/{id}", handler.UpdateEntry) + router.Delete("/{id}", handler.DeleteEntry) } diff --git a/go.mod b/go.mod index 0bae53a..b1060cf 100644 --- a/go.mod +++ b/go.mod @@ -2,31 +2,27 @@ module github.com/OpenCHAMI/cloud-init go 1.21 -require github.com/OpenCHAMI/smd/v2 v2.12.15 +require ( + gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed + github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18 + github.com/OpenCHAMI/smd/v2 v2.12.15 + github.com/go-chi/chi/v5 v5.1.0 + github.com/go-chi/render v1.0.3 + github.com/lestrrat-go/jwx/v2 v2.0.20 +) require ( - github.com/bytedance/sonic v1.9.1 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/ajg/form v1.5.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/gosimple/unidecode v1.0.1 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.4 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/mattn/go-isatty v0.0.19 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect - golang.org/x/arch v0.3.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - google.golang.org/protobuf v1.30.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( @@ -35,10 +31,7 @@ require ( github.com/Cray-HPE/hms-securestorage v1.13.0 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gin-gonic/gin v1.9.1 github.com/go-jose/go-jose/v3 v3.0.0 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/gosimple/slug v1.13.1 github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -54,10 +47,10 @@ require ( github.com/ryanuber/go-glob v1.0.0 // indirect github.com/samber/lo v1.39.0 github.com/sirupsen/logrus v1.9.3 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 1409848..c5012e5 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso= +gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Cray-HPE/hms-base v1.15.0/go.mod h1:+G8KFLPtanLC5lQ602hrf3MDfLTmIXedTavVCOdz5XA= github.com/Cray-HPE/hms-base v1.15.1 h1:+f9cl9BsDWvewvGBPzinmBSU//I7yhwaSUTaNUwxwxQ= @@ -7,70 +9,51 @@ github.com/Cray-HPE/hms-certs v1.4.0 h1:ZyQ50B1e2P81Y7PCbfSFW6O1F0Behi0spScwR6GA github.com/Cray-HPE/hms-certs v1.4.0/go.mod h1:4/NBEi9SWhWxWkZwhk2WDFxQDyXU6PCN5BAr7ejuWLE= github.com/Cray-HPE/hms-securestorage v1.13.0 h1:ut6z9TMtCzL902f9NPxcbtkkDuk9zbX6E30pP8j3k6Q= github.com/Cray-HPE/hms-securestorage v1.13.0/go.mod h1:P4CMKqQVlx/lv+AdyEjNQubZw2FKNyo/IAtFNgQ3VuI= +github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18 h1:oBPtXp9RVm9lk5zTmDLf+Vh21yDHpulBxUqGJQjwQCk= +github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18/go.mod h1:ggNHWgLfW/WRXcE8ZZC4S7UwHif16HVmyowOCWdNSN8= github.com/OpenCHAMI/smd/v2 v2.12.15 h1:WJQCxhetdm0W7NNdrqnuR5dZGmOLayv6o5IFuvIXUO4= github.com/OpenCHAMI/smd/v2 v2.12.15/go.mod h1:CBvxXN8oaelvfpmZnCMkVG3M+pqA/tjC/PXejN1oPf8= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= +github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q= -github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= -github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= -github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -116,13 +99,18 @@ github.com/hashicorp/vault/api v1.9.2/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= +github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.0.20 h1:sAgXuWS/t8ykxS9Bi2Qtn5Qhpakw1wrcjxChudjolCc= +github.com/lestrrat-go/jwx/v2 v2.0.20/go.mod h1:UlCSmKqw+agm5BsOBfEAbTvKsEApaGNqHAEUTv5PJC4= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -142,15 +130,8 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -161,33 +142,23 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= @@ -200,8 +171,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -216,16 +187,15 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -235,8 +205,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -244,9 +212,6 @@ google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRn google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -258,4 +223,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/memstore/ciMemStore.go b/internal/memstore/ciMemStore.go index 58cac26..6566c76 100644 --- a/internal/memstore/ciMemStore.go +++ b/internal/memstore/ciMemStore.go @@ -1,18 +1,16 @@ package memstore import ( - "fmt" "errors" "log" - "reflect" - "github.com/samber/lo" - "github.com/OpenCHAMI/cloud-init/pkg/citypes" "github.com/OpenCHAMI/cloud-init/internal/smdclient" + "github.com/OpenCHAMI/cloud-init/pkg/citypes" + "github.com/samber/lo" ) var ( - NotFoundErr = errors.New("not found") + NotFoundErr = errors.New("not found") ExistingEntryErr = errors.New("Data exists for this entry. Update instead") ) @@ -29,8 +27,6 @@ func NewMemStore() *MemStore { func (m MemStore) Add(name string, ci citypes.CI) error { curr := m.list[name] - fmt.Printf("current: %s\n", curr.CIData.UserData) - fmt.Printf("new: %s\n", ci.CIData.UserData) if ci.CIData.UserData != nil { if curr.CIData.UserData == nil { @@ -41,20 +37,20 @@ func (m MemStore) Add(name string, ci citypes.CI) error { } if ci.CIData.MetaData != nil { - if curr.CIData.MetaData == nil { - curr.CIData.MetaData = ci.CIData.MetaData - } else { - return ExistingEntryErr - } - } - - if ci.CIData.VendorData != nil { - if curr.CIData.VendorData == nil { - curr.CIData.VendorData = ci.CIData.VendorData - } else { - return ExistingEntryErr - } - } + if curr.CIData.MetaData == nil { + curr.CIData.MetaData = ci.CIData.MetaData + } else { + return ExistingEntryErr + } + } + + if ci.CIData.VendorData != nil { + if curr.CIData.VendorData == nil { + curr.CIData.VendorData = ci.CIData.VendorData + } else { + return ExistingEntryErr + } + } m.list[name] = curr return nil @@ -62,22 +58,21 @@ func (m MemStore) Add(name string, ci citypes.CI) error { func (m MemStore) Get(name string, sm *smdclient.SMDClient) (citypes.CI, error) { - //sm := smdclient.NewSMDClient("http://ochami-vm:27779") - ci_merged := new(citypes.CI) id, err := sm.IDfromMAC(name) if err != nil { log.Print(err) + id = name // Fall back to using the given name as an ID } else { - fmt.Printf("xname %s with mac %s found\n", id, name) + log.Printf("xname %s with mac %s found\n", id, name) } - gl,err := sm.GroupMembership(id) + gl, err := sm.GroupMembership(id) if err != nil { log.Print(err) } else if len(gl) > 0 { - fmt.Printf("xname %s is a member of these groups: %s\n",id,gl) + log.Printf("xname %s is a member of these groups: %s\n", id, gl) for g := 0; g < len(gl); g++ { if val, ok := m.list[gl[g]]; ok { @@ -87,19 +82,23 @@ func (m MemStore) Get(name string, sm *smdclient.SMDClient) (citypes.CI, error) } } } else { - fmt.Printf("ID %s is not a member of any groups\n", name) + log.Printf("ID %s is not a member of any groups\n", id) } if val, ok := m.list[id]; ok { ci_merged.CIData.UserData = lo.Assign(ci_merged.CIData.UserData, val.CIData.UserData) ci_merged.CIData.VendorData = lo.Assign(ci_merged.CIData.VendorData, val.CIData.VendorData) ci_merged.CIData.MetaData = lo.Assign(ci_merged.CIData.MetaData, val.CIData.MetaData) + } else { + log.Printf("ID %s has no specific configuration\n", id) } - if !reflect.ValueOf(ci_merged).IsZero() { - return *ci_merged, nil - } else { + if len(ci_merged.CIData.UserData) == 0 && + len(ci_merged.CIData.VendorData) == 0 && + len(ci_merged.CIData.MetaData) == 0 { return citypes.CI{}, NotFoundErr + } else { + return *ci_merged, nil } } @@ -115,11 +114,11 @@ func (m MemStore) Update(name string, ci citypes.CI) error { curr.CIData.UserData = ci.CIData.UserData } if ci.CIData.MetaData != nil { - curr.CIData.MetaData = ci.CIData.MetaData - } + curr.CIData.MetaData = ci.CIData.MetaData + } if ci.CIData.VendorData != nil { - curr.CIData.VendorData = ci.CIData.VendorData - } + curr.CIData.VendorData = ci.CIData.VendorData + } m.list[name] = curr return nil } diff --git a/internal/smdclient/SMDclient.go b/internal/smdclient/SMDclient.go index 60f270d..63174ea 100644 --- a/internal/smdclient/SMDclient.go +++ b/internal/smdclient/SMDclient.go @@ -4,14 +4,13 @@ import ( "encoding/json" "errors" "io" + "log" "net/http" + "os" "strings" "time" - "log" - "github.com/OpenCHAMI/smd/v2/pkg/sm" - "github.com/golang-jwt/jwt" ) // Add client usage examples @@ -25,45 +24,58 @@ var ( // SMDClient is a client for SMD type SMDClient struct { - smdClient *http.Client - smdBaseURL string - accessToken string + smdClient *http.Client + smdBaseURL string + tokenEndpoint string + accessToken string } // NewSMDClient creates a new SMDClient which connects to the SMD server at baseurl -// and uses the provided JWT for authentication -func NewSMDClient(baseurl string, jwt string) *SMDClient { +// and uses the provided JWT server for authentication +func NewSMDClient(baseurl string, jwtURL string) *SMDClient { c := &http.Client{Timeout: 2 * time.Second} return &SMDClient{ smdClient: c, smdBaseURL: baseurl, - accessToken: jwt, + tokenEndpoint: jwtURL, + accessToken: "", } } // getSMD is a helper function to initialize the SMDClient func (s *SMDClient) getSMD(ep string, smd interface{}) error { url := s.smdBaseURL + ep - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return err - } - if s.accessToken != "" { - //validate the JWT without verifying the signature - //if the JWT is not valid, the request will fail - token, _, err := new(jwt.Parser).ParseUnverified(s.accessToken, jwt.MapClaims{}) + var resp *http.Response + // Manage fetching a new JWT if we initially fail + freshToken := false + for true { + req, err := http.NewRequest("GET", url, nil) if err != nil { - return errors.New("poorly formed JWT: " + err.Error()) + return err } - log.Println("Loaded JWT token:", s.accessToken) - log.Println("Claims:", token.Claims) req.Header.Set("Authorization", "Bearer "+s.accessToken) - } else { - return errors.New("poorly formed JWT") - } - resp, err := s.smdClient.Do(req) - if err != nil { - return err + resp, err = s.smdClient.Do(req) + if err != nil { + return err + } + if resp.StatusCode == http.StatusUnauthorized { + // Request failed; handle appropriately (based on whether or not + // this was a fresh JWT) + log.Println("Cached JWT was rejected by SMD") + if !freshToken { + log.Println("Fetching new JWT and retrying...") + s.RefreshToken() + freshToken = true + } else { + log.Fatalln("SMD authentication failed, even with a fresh" + + " JWT. Something has gone terribly wrong; exiting to" + + " avoid invalid request spam.") + os.Exit(2) + } + } else { + // Request succeeded; we're done here + break + } } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) @@ -88,7 +100,7 @@ func (s *SMDClient) IDfromMAC(mac string) (string, error) { } } } - return "", errors.New("MAC " + mac + " not found for an xname in CompenentEndpoints") + return "", errors.New("MAC " + mac + " not found for an xname in ComponentEndpoints") } // GroupMembership returns the group labels for the xname with the given ID diff --git a/internal/smdclient/oidc.go b/internal/smdclient/oidc.go new file mode 100644 index 0000000..803fc35 --- /dev/null +++ b/internal/smdclient/oidc.go @@ -0,0 +1,39 @@ +package smdclient + +import ( + "encoding/json" + "io" + "net/http" +) + +// Structure of a token reponse from OIDC server +type oidcTokenData struct { + Access_token string `json:"access_token"` + Expires_in int `json:"expires_in"` + Scope string `json:"scope"` + Token_type string `json:"token_type"` +} + +// Refresh the cached access token, using the provided JWT server +// TODO: OPAAL returns a token without having to perform the usual OAuth2 +// authorization grant. Support for said grant should probably be implemented +// at some point. +func (s *SMDClient) RefreshToken() error { + // Request new token from OIDC server + r, err := http.Get(s.tokenEndpoint) + if err != nil { + return err + } + body, err := io.ReadAll(r.Body) + if err != nil { + return err + } + // Decode server's response to the expected structure + var tokenResp oidcTokenData + if err = json.Unmarshal(body, &tokenResp); err != nil { + return err + } + // Extract and store the JWT itself + s.accessToken = tokenResp.Access_token + return nil +}