Skip to content

Commit

Permalink
Add JWT issuing and validation. Change Harvester onboarding and authe…
Browse files Browse the repository at this point in the history
…ntication processes. (#151)

Add JWT issuing and validation
Change Harvester onboarding and authentication processes.

Signed-off-by: Max Lambrecht <[email protected]>
  • Loading branch information
Max Lambrecht authored May 11, 2023
1 parent decf8cb commit ba2c8bd
Show file tree
Hide file tree
Showing 42 changed files with 2,048 additions and 531 deletions.
5 changes: 3 additions & 2 deletions cmd/server/cli/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"fmt"
"strings"

"github.com/HewlettPackard/galadriel/cmd/server/util"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -29,12 +30,12 @@ var tokenCmd = &cobra.Command{
return err
}

at, err := c.GenerateJoinToken(trustDomain)
joinToken, err := c.GenerateJoinToken(trustDomain)
if err != nil {
return err
}

fmt.Println("Join Token: " + at.Token)
fmt.Printf("Token: %s", strings.ReplaceAll(joinToken, "\"", ""))
return nil
},
}
Expand Down
29 changes: 8 additions & 21 deletions cmd/server/util/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var (
listTrustDomainsURL = fmt.Sprintf(localURL, "listTrustDomains")
createRelationshipURL = fmt.Sprintf(localURL, "createRelationship")
listRelationshipsURL = fmt.Sprintf(localURL, "listRelationships")
generateTokenURL = fmt.Sprintf(localURL, "generateToken")
joinTokenURL = fmt.Sprintf(localURL, "trust-domain/%s/join-token")
)

// ServerLocalClient represents a local client of the Galadriel Server.
Expand All @@ -36,7 +36,7 @@ type ServerLocalClient interface {
ListTrustDomains() ([]*entity.TrustDomain, error)
CreateRelationship(r *entity.Relationship) error
ListRelationships() ([]*entity.Relationship, error)
GenerateJoinToken(trustDomain spiffeid.TrustDomain) (*entity.JoinToken, error)
GenerateJoinToken(trustDomain spiffeid.TrustDomain) (string, error)
}

// TODO: improve this adding options for the transport, dialcontext, and http.Client.
Expand Down Expand Up @@ -154,31 +154,18 @@ func (c serverClient) ListRelationships() ([]*entity.Relationship, error) {
return rels, nil
}

func (c serverClient) GenerateJoinToken(td spiffeid.TrustDomain) (*entity.JoinToken, error) {
b, err := json.Marshal(entity.TrustDomain{Name: td})
func (c serverClient) GenerateJoinToken(td spiffeid.TrustDomain) (string, error) {
joinTokenURL := fmt.Sprintf(joinTokenURL, td)
r, err := c.client.Get(joinTokenURL)
if err != nil {
return nil, err
}

r, err := c.client.Post(generateTokenURL, contentType, bytes.NewReader(b))
if err != nil {
return nil, err
return "", err
}
defer r.Body.Close()

body, err := io.ReadAll(r.Body)
if err != nil {
return nil, err
}

var createdToken entity.JoinToken
if err = json.Unmarshal(body, &createdToken); err != nil {
if len(body) == 0 {
return nil, errors.New("failed to generate token")
}

return nil, errors.New(string(body))
return "", err
}

return &createdToken, nil
return string(body), nil
}
5 changes: 4 additions & 1 deletion conf/server/server.conf
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ server {

providers {
# X509CA "disk": Uses a ROOT CA loaded from disk to issue X509 certificates.
x509ca "disk" {
X509CA "disk" {
# Path to the root CA private key file. PEM format.
key_file_path = "./conf/server/dummy_root_ca.key"
# Path to the root CA certificate file. PEM format.
cert_file_path = "./conf/server/dummy_root_ca.crt"
}

# KeyManager "memory": A key manager for generating keys and signing certificates that stores keys in memory.
KeyManager "memory" {}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.19
require (
github.com/deepmap/oapi-codegen v1.12.4
github.com/getkin/kin-openapi v0.116.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang-migrate/migrate/v4 v4.15.2
github.com/google/uuid v1.3.0
github.com/hashicorp/hcl v1.0.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL
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-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-migrate/migrate/v4 v4.15.2 h1:vU+M05vs6jWHKDdmE1Ecwj0BznygFc4QsdRe2E/L7kc=
github.com/golang-migrate/migrate/v4 v4.15.2/go.mod h1:f2toGLkYqD3JH+Todi4aZ2ZdbeUNx4sIwiOK96rE9Lw=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
Expand Down
110 changes: 55 additions & 55 deletions pkg/common/api/schemas.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions pkg/common/api/schemas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,4 @@ components:
harvester_auth:
type: "http"
scheme: "bearer"
bearerFormat: "JWT"
join_token:
type: "http"
scheme: "bearer"
bearerFormat: "UUID"
bearerFormat: "JWT"
88 changes: 88 additions & 0 deletions pkg/common/jwt/issuer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package jwt

import (
"context"
"crypto"
"fmt"
"time"

"github.com/golang-jwt/jwt/v4"
"github.com/jmhodges/clock"
"github.com/spiffe/go-spiffe/v2/spiffeid"
)

const kidHeader = "kid"

// Issuer is the interface used to sign JWTs.
type Issuer interface {
// IssueJWT issues a JWT and returns the JWT.
IssueJWT(context.Context, *JWTParams) (string, error)
}

// JWTParams holds the parameters for issuing a JWT.
type JWTParams struct {
Issuer string
Subject spiffeid.TrustDomain
Audience []string
TTL time.Duration
}

// Config is the configuration for the JWTCA
type Config struct {
// signer is an interface for an opaque private key
// that can be used for signing operations
Signer crypto.Signer

// Kid is the id of the key used for signing
Kid string
}

// JWTCA is an implementation of the Issuer interface that issues JWTs.
type JWTCA struct {
// signer is an interface for an opaque private key
// that can be used for signing operations
signer crypto.Signer

// kid is the id of the key used for signing
kid string

clk clock.Clock
}

// NewJWTCA creates a new JWTCA.
func NewJWTCA(c *Config) (*JWTCA, error) {
if c.Signer == nil {
return nil, fmt.Errorf("signer is required")
}
if c.Kid == "" {
return nil, fmt.Errorf("kid is required")
}

return &JWTCA{
kid: c.Kid,
signer: c.Signer,
clk: clock.New(),
}, nil
}

func (ca *JWTCA) IssueJWT(ctx context.Context, params *JWTParams) (string, error) {
expiresAt := ca.clk.Now().Add(params.TTL)
now := ca.clk.Now()

registeredClaims := jwt.RegisteredClaims{
Issuer: params.Issuer,
Subject: params.Subject.String(),
Audience: params.Audience,
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(expiresAt),
}

token := jwt.NewWithClaims(jwt.SigningMethodRS256, registeredClaims)
token.Header[kidHeader] = ca.kid
signedToken, err := token.SignedString(ca.signer)
if err != nil {
return "", fmt.Errorf("failed to sign token: %w", err)
}

return signedToken, nil
}
Loading

0 comments on commit ba2c8bd

Please sign in to comment.