internal/pkg/oidc/token.go (107 lines of code) (raw):
package oidc
import (
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"time"
jose "github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
)
type oidcTokenClaim struct {
Issuer string `json:"iss,omitempty"`
Subject string `json:"sub,omitempty"`
kid string
keyFile string
Role string `json:"role,omitempty"`
Email string `json:"email,omitempty"`
Audience []string `json:"aud,omitempty"`
Groups []string `json:"groups,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
KubernetesAccess string `json:"kubernetesAccess,omitempty"`
}
type JWTOpt func(*oidcTokenClaim)
func NewJWT(issuerName, kid, keyFile string, opts ...JWTOpt) (string, error) {
now := time.Now()
o := &oidcTokenClaim{
Issuer: fmt.Sprintf("https://%s", issuerName),
Audience: []string{},
ExpiresAt: now.Add(time.Hour * 24).Unix(),
IssuedAt: now.Unix(),
NotBefore: now.Unix(),
Groups: []string{},
kid: kid,
keyFile: keyFile,
}
for _, opt := range opts {
opt(o)
}
return o.generateToken()
}
func WithEmail(email string) JWTOpt {
return func(o *oidcTokenClaim) {
o.Email = email
}
}
func WithGroup(group string) JWTOpt {
return func(o *oidcTokenClaim) {
o.Groups = append(o.Groups, group)
}
}
func WithRole(role string) JWTOpt {
return func(o *oidcTokenClaim) {
o.Role = role
}
}
func WithKubernetesAccess(access bool) JWTOpt {
return func(o *oidcTokenClaim) {
o.KubernetesAccess = fmt.Sprintf("%t", access)
}
}
func WithAudience(audience string) JWTOpt {
return func(o *oidcTokenClaim) {
o.Audience = append(o.Audience, audience)
}
}
func WithSubject(subject string) JWTOpt {
return func(o *oidcTokenClaim) {
o.Subject = subject
}
}
func (o *oidcTokenClaim) generateToken() (string, error) {
keyContent, err := os.ReadFile(o.keyFile)
if err != nil {
return "", fmt.Errorf("could not read key file: %v", err)
}
block, _ := pem.Decode(keyContent)
if block == nil {
return "", fmt.Errorf("decoding PEM file %s", o.keyFile)
}
privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("parsing key content of %s: %v", o.keyFile, err)
}
jwkKey := &jose.JSONWebKey{
Algorithm: string(jose.RS256),
Key: privKey,
KeyID: o.kid,
Use: "sig",
}
signer, err := jose.NewSigner(
jose.SigningKey{
Algorithm: jose.RS256,
Key: jwkKey,
},
nil,
)
if err != nil {
return "", fmt.Errorf("failed to create signer: %v", err)
}
token, err := jwt.Signed(signer).Claims(o).CompactSerialize()
if err != nil {
return "", fmt.Errorf("signing token: %v", err)
}
return token, nil
}