Skip to content

Commit

Permalink
Implement JWT-based authentication for secure route
Browse files Browse the repository at this point in the history
This copies much JWKS helper code (`cmd/cloud-init-server/auth.go`) from
https://github.com/OpenCHAMI/smd/blob/cdac4f1a606401a2b150513b935143238e3eb026/cmd/smd/routers.go
with certain irrelevant-to-us parts stripped out.
  • Loading branch information
LRitzdorf committed Jul 8, 2024
1 parent d1779ea commit e884218
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 7 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added an additional URL endpoint (`/cloud-init-secure`) which will require JWT authentication for access
- Added an additional URL endpoint (`/cloud-init-secure`) which requires JWT authentication for access

### Changed

Expand Down
61 changes: 61 additions & 0 deletions cmd/cloud-init-server/auth.go
Original file line number Diff line number Diff line change
@@ -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
}
36 changes: 31 additions & 5 deletions cmd/cloud-init-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,45 @@ package main

import (
"flag"
"fmt"
"net/http"

"github.com/OpenCHAMI/cloud-init/internal/memstore"
"github.com/OpenCHAMI/cloud-init/internal/smdclient"
"github.com/OpenCHAMI/jwtauth/v5"
"github.com/go-chi/chi/v5"
)

var (
ciEndpoint = ":27777"
smdEndpoint = "http://smd:27779"
smdToken = "" // jwt for access to smd
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(&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()

// 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()
sm := smdclient.NewSMDClient(smdEndpoint, smdToken)
Expand All @@ -31,11 +51,17 @@ func main() {
router_unsec := newCiRouter(ciHandler)
router.Mount("/cloud-init", router_unsec)

// Secured datastore and router
store_sec := memstore.NewMemStore()
ciHandler_sec := NewCiHandler(store_sec, sm)
router_sec := newCiRouter(ciHandler_sec)
router.Mount("/cloud-init-secure", router_sec)
if secureRouteEnable {
// Secured datastore and router
store_sec := memstore.NewMemStore()
ciHandler_sec := NewCiHandler(store_sec, sm)
router_sec := newCiRouter(ciHandler_sec)
router_sec.Use(
jwtauth.Verifier(keyset),
jwtauth.Authenticator(keyset),
)
router.Mount("/cloud-init-secure", router_sec)
}

// Serve all routes
http.ListenAndServe(ciEndpoint, router)
Expand Down
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@ go 1.21

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/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/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/stretchr/testify v1.8.4 // indirect
github.com/segmentio/asm v1.2.0 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
)

Expand Down
19 changes: 19 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ 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=
Expand All @@ -23,6 +25,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
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=
Expand Down Expand Up @@ -101,6 +105,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/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=
Expand Down Expand Up @@ -132,6 +148,8 @@ 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=
Expand All @@ -140,6 +158,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
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.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=
Expand Down

0 comments on commit e884218

Please sign in to comment.