Skip to content

Commit fb7eebf

Browse files
committed
Add jwks generation and example
1 parent 4b3c295 commit fb7eebf

File tree

6 files changed

+187
-14
lines changed

6 files changed

+187
-14
lines changed

examples/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ custom-resources/foreign-namespace-upstreams/cafe-secret.yaml
1818
custom-resources/grpc-upstreams/greeter-secret.yaml
1919
custom-resources/ingress-mtls/tls-secret.yaml
2020
custom-resources/jwks/tls-secret.yaml
21+
custom-resources/jwt/jwk-secret.yaml
2122
custom-resources/oidc-fclo/tls-secret.yaml
2223
custom-resources/oidc/tls-secret.yaml
2324
custom-resources/rate-limit-tiered-jwt-claim/cafe-secret.yaml

examples/custom-resources/jwt/jwk-secret.yaml

Lines changed: 0 additions & 7 deletions
This file was deleted.

hack/secrets.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,5 +781,20 @@
781781
"tests/suite/test_auth_basic_secrets.py - needed for invalid auth basic secret tests"
782782
]
783783
}
784+
],
785+
"jwks": [
786+
{
787+
"secretName": "jwk-secret",
788+
"fileName": "example-jwt-jwks-secret.yaml",
789+
"key": "fantasticjwt",
790+
"kid": "0001",
791+
"kty": "oct",
792+
"symlinks": [
793+
"/examples/custom-resources/jwt/jwk-secret.yaml"
794+
],
795+
"usedIn": [
796+
"Used in jwt example in custom-resources"
797+
]
798+
}
784799
]
785800
}

hack/tls-cert-gen/htpasswd-gen.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,7 @@ func createKubeHTPasswdSecretYaml(secret htpasswdSecret, data []byte) ([]byte, e
8989
s.Namespace = secret.Namespace
9090
}
9191

92-
sb, err := yaml.Marshal(s)
93-
if err != nil {
94-
return nil, fmt.Errorf("marshaling kubernetes secret into yaml %v: %w", s, err)
95-
}
96-
97-
return sb, nil
92+
return yaml.Marshal(s)
9893
}
9994

10095
func removeHtpasswdFiles(logger *slog.Logger, secret htpasswdSecret) error {

hack/tls-cert-gen/jwks-gen.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package main
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"fmt"
7+
"log/slog"
8+
"os"
9+
"path/filepath"
10+
11+
log "github.com/nginx/kubernetes-ingress/internal/logger"
12+
v1 "k8s.io/api/core/v1"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"sigs.k8s.io/yaml"
15+
)
16+
17+
type jwkSecret struct {
18+
SecretName string `json:"secretName"`
19+
Namespace string `json:"namespace,omitempty"`
20+
FileName string `json:"filename"`
21+
Symlinks []string `json:"symlinks,omitempty"`
22+
UsedIn []string `json:"usedIn,omitempty"`
23+
Key string `json:"key"`
24+
Kid string `json:"kid"`
25+
Kty string `json:"kty"`
26+
SecretType v1.SecretType `json:"secretType,omitempty"`
27+
JwksKey string `json:"jwksKey,omitempty"`
28+
}
29+
30+
type JWK struct {
31+
Kty string `json:"kty"`
32+
Kid string `json:"kid"`
33+
Alg string `json:"alg"`
34+
K string `json:"k,omitempty"`
35+
}
36+
37+
type JWKS struct {
38+
Keys []JWK `json:"keys"`
39+
}
40+
41+
func generateJwksFile(secret jwkSecret, projectRoot string) error {
42+
jwks, err := generateJwks(secret.Kid, secret.Kty, secret.Key)
43+
if err != nil {
44+
return fmt.Errorf("generating JWKS for secret %s: %w", secret.SecretName, err)
45+
}
46+
47+
fileContents, err := createKubeJwksSecretYaml(secret, jwks)
48+
if err != nil {
49+
return fmt.Errorf("writing valid file for %s: %w", secret.FileName, err)
50+
}
51+
52+
err = writeFiles(fileContents, projectRoot, secret.FileName, secret.Symlinks)
53+
if err != nil {
54+
return fmt.Errorf("writing file for %s: %w", secret.FileName, err)
55+
}
56+
57+
return nil
58+
}
59+
60+
func generateJwks(kid, kty, key string) ([]byte, error) {
61+
jwks := JWKS{
62+
Keys: []JWK{
63+
{
64+
Kty: "Oct", // key type
65+
Kid: "0001", // any unique identifier
66+
Alg: "HS256", // signing algorithm
67+
K: base64.StdEncoding.EncodeToString([]byte(key)),
68+
},
69+
},
70+
}
71+
72+
if kty != "" {
73+
jwks.Keys[0].Kty = kty
74+
}
75+
if kid != "" {
76+
jwks.Keys[0].Kid = kid
77+
}
78+
79+
return json.Marshal(jwks)
80+
}
81+
82+
func createKubeJwksSecretYaml(secret jwkSecret, data []byte) ([]byte, error) {
83+
key := "jwk"
84+
if secret.JwksKey != "" {
85+
key = secret.JwksKey
86+
}
87+
s := v1.Secret{
88+
TypeMeta: metav1.TypeMeta{
89+
APIVersion: "v1",
90+
Kind: "Secret",
91+
},
92+
ObjectMeta: metav1.ObjectMeta{
93+
Name: secret.SecretName,
94+
},
95+
Type: "nginx.org/jwks",
96+
Data: map[string][]byte{
97+
key: data,
98+
},
99+
}
100+
101+
if secret.SecretType != "" {
102+
s.Type = secret.SecretType
103+
}
104+
105+
if secret.Namespace != "" {
106+
s.Namespace = secret.Namespace
107+
}
108+
109+
return yaml.Marshal(s)
110+
}
111+
112+
func removeJwksFiles(logger *slog.Logger, secret jwkSecret) error {
113+
filePath := filepath.Join(projectRoot, realSecretDirectory, secret.FileName)
114+
log.Debugf(logger, "Removing file %s", filePath)
115+
if _, err := os.Stat(filePath); !os.IsNotExist(err) {
116+
err := os.Remove(filepath.Join(projectRoot, realSecretDirectory, secret.FileName))
117+
if err != nil {
118+
return fmt.Errorf("failed to remove file: %s %w", secret.FileName, err)
119+
}
120+
}
121+
122+
for _, symlink := range secret.Symlinks {
123+
log.Debugf(logger, "Removing symlink %s", symlink)
124+
if _, err := os.Lstat(filepath.Join(projectRoot, symlink)); !os.IsNotExist(err) {
125+
err = os.Remove(filepath.Join(projectRoot, symlink))
126+
if err != nil {
127+
return fmt.Errorf("failed to remove symlink: %s %w", symlink, err)
128+
}
129+
}
130+
}
131+
return nil
132+
}

hack/tls-cert-gen/main.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type secretsTypes struct {
2828
Certs []yamlSecret `json:"certs,omitempty"`
2929
Mtls []mtlsBundle `json:"mtls,omitempty"`
3030
Htpasswds []htpasswdSecret `json:"htpasswds,omitempty"`
31+
Jwks []jwkSecret `json:"jwks,omitempty"`
3132
}
3233

3334
var secretsTypesData secretsTypes
@@ -67,10 +68,46 @@ func main() {
6768
log.Fatalf(logger, "generateMTLSBundles: %v", err)
6869
}
6970

70-
_, err = generateHtpasswdFiles(logger, secretsTypesData.Htpasswds, filenames, cleanPtr)
71+
filenames, err = generateHtpasswdFiles(logger, secretsTypesData.Htpasswds, filenames, cleanPtr)
7172
if err != nil {
7273
log.Fatalf(logger, "generateHtpasswdFiles: %v", err)
7374
}
75+
76+
_, err = generateJwksFiles(logger, secretsTypesData.Jwks, filenames, cleanPtr)
77+
if err != nil {
78+
log.Fatalf(logger, "generateJwksFiles: %v", err)
79+
}
80+
}
81+
82+
func generateJwksFiles(logger *slog.Logger, secrets []jwkSecret, filenames map[string]struct{}, cleanPtr *bool) (map[string]struct{}, error) {
83+
for _, secret := range secrets {
84+
if _, ok := filenames[secret.FileName]; ok {
85+
return nil, fmt.Errorf("secret contains duplicated files: %v", secret.FileName)
86+
}
87+
88+
filenames[secret.FileName] = struct{}{}
89+
90+
for _, symlink := range secret.Symlinks {
91+
if _, ok := filenames[symlink]; ok {
92+
return nil, fmt.Errorf("secret contains duplicated symlink for file %s: %s", secret.FileName, symlink)
93+
}
94+
95+
filenames[symlink] = struct{}{}
96+
}
97+
98+
if *cleanPtr {
99+
err := removeJwksFiles(logger, secret)
100+
if err != nil {
101+
return nil, fmt.Errorf("failed to remove secret files: %s %w", secret.FileName, err)
102+
}
103+
continue
104+
}
105+
err := generateJwksFile(secret, projectRoot)
106+
if err != nil {
107+
return nil, fmt.Errorf("failed to print JWKS file: %s %w", secret.FileName, err)
108+
}
109+
}
110+
return filenames, nil
74111
}
75112

76113
func generateHtpasswdFiles(logger *slog.Logger, secrets []htpasswdSecret, filenames map[string]struct{}, cleanPtr *bool) (map[string]struct{}, error) {

0 commit comments

Comments
 (0)