Skip to content

Commit 573be80

Browse files
committed
add api key secret validation to reject duplicated keys, remove repeated maps in config, remove reject code, add unit tests
Signed-off-by: Haywood Shannon <[email protected]> Signed-off-by: Haywood Shannon <[email protected]>
1 parent 512e27b commit 573be80

File tree

14 files changed

+200
-102
lines changed

14 files changed

+200
-102
lines changed

examples/custom-resources/api-key/api-key-policy-2.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,4 @@ spec:
99
- "X-header-name"
1010
query:
1111
- "queryName"
12-
rejectCodes: # optional
13-
notSupplied: 408
14-
noMatch: 410
1512
clientSecret: api-key-client-secret-2

examples/custom-resources/api-key/api-key-policy.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,4 @@ spec:
1111
- "some-other-header"
1212
query:
1313
- "queryName"
14-
# rejectCodes: # optional
15-
# notSupplied: 401
16-
# noMatch: 403
1714
clientSecret: api-key-client-secret-1

examples/custom-resources/api-key/api-key-secret-1.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ metadata:
55
type: nginx.org/apikey
66
data:
77
client1: cGFzc3dvcmQ=
8-
client2: N2ViNDMwOGItY2Q1Yi00NDEzLWI0NTUtYjMyZmQ4OTg2MmZk
8+
client2: N2ViNDMwOGItY2Q1Yi00NDEzLWI0NTUtYjMyZmQ4OTg2MmZk

examples/custom-resources/api-key/cafe-virtual-server-2.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,9 @@ spec:
2424
pass: coffee
2525
policies:
2626
- name: api-key-policy-2
27+
- path: /coffee2
28+
action:
29+
pass: coffee
30+
policies:
31+
- name: api-key-policy
32+

examples/custom-resources/api-key/coffee-virtual-server-route.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ spec:
1414
pass: coffee
1515
policies:
1616
- name: api-key-policy-2
17+
- path: /coffee2
18+
action:
19+
pass: coffee
20+
policies:
21+
- name: api-key-policy

internal/configs/configurator.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,9 +1991,7 @@ func (cnf *Configurator) AddOrUpdateSecret(secret *api_v1.Secret) string {
19911991
// OIDC ClientSecret is not required on the filesystem, it is written directly to the config file.
19921992
return ""
19931993
case secrets.SecretTypeAPIKey:
1994-
glog.Infof("adding apikey secret: %s", secret.Name)
1995-
// OIDC ClientSecret is not required on the filesystem, it is written directly to the config file.
1996-
1994+
// APIKey ClientSecret is not required on the filesystem, it is written directly to the config file.
19971995
return ""
19981996
default:
19991997
return cnf.addOrUpdateTLSSecret(secret)

internal/configs/version1/nginx.tmpl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{{- /*gotype: github.com/nginxinc/kubernetes-ingress/internal/configs/version1.MainConfig*/ -}}
2-
32
worker_processes {{.WorkerProcesses}};
43
{{- if .WorkerRlimitNofile}}
54
worker_rlimit_nofile {{.WorkerRlimitNofile}};{{end}}

internal/configs/version2/http.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,9 @@ type OIDC struct {
139139
}
140140

141141
type APIKey struct {
142-
Header []string
143-
Query []string
144-
RejectCodeNotSupplied int
145-
RejectCodeNoMatch int
146-
Clients []Client
147-
MapName string
148-
}
149-
150-
type Client struct {
151-
ClientID string
152-
EncryptedKey string
142+
Header []string
143+
Query []string
144+
MapName string
153145
}
154146

155147
// WAF defines WAF configuration.

internal/configs/version2/nginx-plus.virtualserver.tmpl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
{{- /*gotype: github.com/nginxinc/kubernetes-ingress/internal/configs/version2.VirtualServerConfig*/ -}}
2-
3-
42
{{ range $u := .Upstreams }}
53
upstream {{ $u.Name }} {
64
zone {{ $u.Name }} {{ if ne $u.UpstreamZoneSize "0" }}{{ $u.UpstreamZoneSize }}{{ else }}512k{{ end }};

internal/configs/virtualserver.go

Lines changed: 81 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -416,18 +416,18 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
416416
vsNamespace: vsEx.VirtualServer.Namespace,
417417
vsName: vsEx.VirtualServer.Name,
418418
}
419-
policiesCfg, mapsFromPolicies := vsc.generatePolicies(ownerDetails, vsEx.VirtualServer.Spec.Policies, vsEx.Policies, specContext, policyOpts)
420-
421-
maps = append(maps, mapsFromPolicies...)
419+
policiesCfg := vsc.generatePolicies(ownerDetails, vsEx.VirtualServer.Spec.Policies, vsEx.Policies, specContext, policyOpts)
422420
if policiesCfg.JWKSAuthEnabled {
423421
jwtAuthKey := policiesCfg.JWTAuth.Key
424422
policiesCfg.JWTAuthList = make(map[string]*version2.JWTAuth)
425423
policiesCfg.JWTAuthList[jwtAuthKey] = policiesCfg.JWTAuth
426424
}
427-
//
428-
//if policiesCfg.APIKey {
429-
// apiKey := policiesCfg.APIKey.MapName
430-
//}
425+
426+
if policiesCfg.APIKeyEnabled {
427+
apiMapName := policiesCfg.APIKey.MapName
428+
policiesCfg.APIKeyClientMap = make(map[string][]APIKeyClient)
429+
policiesCfg.APIKeyClientMap[apiMapName] = policiesCfg.APIKeyClients
430+
}
431431

432432
dosCfg := generateDosCfg(dosResources[""])
433433

@@ -571,9 +571,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
571571
vsNamespace: vsEx.VirtualServer.Namespace,
572572
vsName: vsEx.VirtualServer.Name,
573573
}
574-
routePoliciesCfg, mapsFromPolicies := vsc.generatePolicies(ownerDetails, r.Policies, vsEx.Policies, routeContext, policyOpts)
575-
maps = append(maps, mapsFromPolicies...)
576-
574+
routePoliciesCfg := vsc.generatePolicies(ownerDetails, r.Policies, vsEx.Policies, routeContext, policyOpts)
577575
if policiesCfg.OIDC {
578576
routePoliciesCfg.OIDC = policiesCfg.OIDC
579577
}
@@ -589,6 +587,16 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
589587
policiesCfg.JWTAuthList[jwtAuthKey] = routePoliciesCfg.JWTAuth
590588
}
591589
}
590+
if routePoliciesCfg.APIKeyEnabled {
591+
policiesCfg.APIKeyEnabled = routePoliciesCfg.APIKeyEnabled
592+
apiMapName := routePoliciesCfg.APIKey.MapName
593+
if policiesCfg.APIKeyClientMap == nil {
594+
policiesCfg.APIKeyClientMap = make(map[string][]APIKeyClient)
595+
}
596+
if _, exists := policiesCfg.APIKeyClientMap[apiMapName]; !exists {
597+
policiesCfg.APIKeyClientMap[apiMapName] = routePoliciesCfg.APIKeyClients
598+
}
599+
}
592600
limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...)
593601

594602
dosRouteCfg := generateDosCfg(dosResources[r.Path])
@@ -703,8 +711,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
703711
policyRefs = r.Policies
704712
context = subRouteContext
705713
}
706-
routePoliciesCfg, mapsFromPolicies := vsc.generatePolicies(ownerDetails, policyRefs, vsEx.Policies, context, policyOpts)
707-
maps = append(maps, mapsFromPolicies...)
714+
routePoliciesCfg := vsc.generatePolicies(ownerDetails, policyRefs, vsEx.Policies, context, policyOpts)
708715
if policiesCfg.OIDC {
709716
routePoliciesCfg.OIDC = policiesCfg.OIDC
710717
}
@@ -720,6 +727,17 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
720727
policiesCfg.JWTAuthList[jwtAuthKey] = routePoliciesCfg.JWTAuth
721728
}
722729
}
730+
if routePoliciesCfg.APIKeyEnabled {
731+
policiesCfg.APIKeyEnabled = routePoliciesCfg.APIKeyEnabled
732+
apiMapName := routePoliciesCfg.APIKey.MapName
733+
if policiesCfg.APIKeyClientMap == nil {
734+
policiesCfg.APIKeyClientMap = make(map[string][]APIKeyClient)
735+
}
736+
if _, exists := policiesCfg.APIKeyClientMap[apiMapName]; !exists {
737+
policiesCfg.APIKeyClientMap[apiMapName] = routePoliciesCfg.APIKeyClients
738+
}
739+
}
740+
723741
limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...)
724742

725743
dosRouteCfg := generateDosCfg(dosResources[r.Path])
@@ -787,6 +805,10 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(
787805
}
788806
}
789807

808+
for mapName, apiKeyClients := range policiesCfg.APIKeyClientMap {
809+
maps = append(maps, *generateAPIKeyClientMap(mapName, apiKeyClients))
810+
}
811+
790812
httpSnippets := generateSnippets(vsc.enableSnippets, vsEx.VirtualServer.Spec.HTTPSnippets, []string{})
791813
serverSnippets := generateSnippets(
792814
vsc.enableSnippets,
@@ -870,8 +892,10 @@ type policiesCfg struct {
870892
IngressMTLS *version2.IngressMTLS
871893
EgressMTLS *version2.EgressMTLS
872894
OIDC bool
895+
APIKeyEnabled bool
873896
APIKey *version2.APIKey
874-
APIKeyList map[string]*version2.APIKey
897+
APIKeyClients []APIKeyClient
898+
APIKeyClientMap map[string][]APIKeyClient
875899
WAF *version2.WAF
876900
ErrorReturn *version2.Return
877901
BundleValidator bundleValidator
@@ -886,6 +910,11 @@ type internalBundleValidator struct {
886910
bundlePath string
887911
}
888912

913+
type APIKeyClient struct {
914+
ClientID string
915+
HashedKey string
916+
}
917+
889918
func (i internalBundleValidator) validate(bundle string) (string, error) {
890919
bundle = path.Join(i.bundlePath, bundle)
891920
_, err := os.Stat(bundle)
@@ -1308,19 +1337,17 @@ func (p *policiesCfg) addAPIKeyConfig(
13081337
polNamespace string,
13091338
secretRefs map[string]*secrets.SecretReference,
13101339
context string,
1311-
) (*validationResults, *version2.Map) {
1340+
) *validationResults {
13121341
res := newValidationResults()
1313-
var clients []version2.Client
13141342
if p.APIKey != nil {
13151343
res.addWarningf(
13161344
"Multiple APIKey policies in the same context is not valid. APIKey policy %s will be ignored",
13171345
polKey,
13181346
)
13191347
res.isError = true
1320-
return res, nil
1348+
return res
13211349
}
13221350

1323-
// if apiKey.ClientSecret != "" {
13241351
secretKey := fmt.Sprintf("%v/%v", polNamespace, apiKey.ClientSecret)
13251352
glog.Infof("secretKey: %v", secretKey)
13261353
secretRef := secretRefs[secretKey]
@@ -1333,57 +1360,65 @@ func (p *policiesCfg) addAPIKeyConfig(
13331360
if secretType != "" && secretType != secrets.SecretTypeAPIKey {
13341361
res.addWarningf("API Key policy %s references a secret %s of a wrong type '%s', must be '%s'", polKey, secretKey, secretType, secrets.SecretTypeAPIKey)
13351362
res.isError = true
1336-
return res, nil
1363+
return res
13371364
} else if secretRef.Error != nil {
13381365
res.addWarningf("API Key %s references an invalid secret %s: %v", polKey, secretKey, secretRef.Error)
13391366
res.isError = true
1340-
return res, nil
1367+
return res
1368+
}
1369+
1370+
p.APIKeyClients = generateAPIKeyClients(secretRef.Secret.Data)
1371+
1372+
mapName := fmt.Sprintf("apikey_auth_client_name_%s", strings.Split(strings.Replace(polKey, "-", "_", -1), "/")[1])
1373+
p.APIKey = &version2.APIKey{
1374+
Header: apiKey.SuppliedIn.Header,
1375+
Query: apiKey.SuppliedIn.Query,
1376+
MapName: mapName,
13411377
}
1378+
p.APIKeyEnabled = true
1379+
return res
1380+
}
13421381

1343-
for clientID, clientSecret := range secretRef.Secret.Data {
1382+
func generateAPIKeyClients(secretData map[string][]byte) []APIKeyClient {
1383+
var clients []APIKeyClient
1384+
for clientID, apiKey := range secretData {
13441385

13451386
h := sha256.New()
1346-
h.Write([]byte(clientSecret))
1387+
h.Write([]byte(apiKey))
13471388
sha256Hash := hex.EncodeToString(h.Sum(nil))
13481389
base64Str := base64.URLEncoding.EncodeToString(h.Sum(nil))
13491390

1350-
glog.Infof("clientSecret %s", clientSecret)
1391+
glog.Infof("apiKey %s", apiKey)
13511392
glog.Infof("sha %s", sha256Hash)
13521393
glog.Infof("base64Str %s", base64Str)
1353-
clients = append(clients, version2.Client{ClientID: clientID, EncryptedKey: sha256Hash}) //
1394+
clients = append(clients, APIKeyClient{ClientID: clientID, HashedKey: sha256Hash}) //
13541395
}
1396+
return clients
1397+
}
13551398

1356-
default_parameter := version2.Parameter{
1399+
func generateAPIKeyClientMap(mapName string, apiKeyClients []APIKeyClient) *version2.Map {
1400+
glog.Infof("mapName: %v, apiKeyClients: %v", mapName, apiKeyClients)
1401+
1402+
defaultParam := version2.Parameter{
13571403
Value: "default",
13581404
Result: "\"\"",
13591405
}
13601406

1361-
params := []version2.Parameter{default_parameter}
1362-
for _, client := range clients {
1407+
params := []version2.Parameter{defaultParam}
1408+
for _, client := range apiKeyClients {
13631409
params = append(params, version2.Parameter{
1364-
Value: fmt.Sprintf("\"%s\"", client.EncryptedKey),
1410+
Value: fmt.Sprintf("\"%s\"", client.HashedKey),
13651411
Result: fmt.Sprintf("\"%s\"", client.ClientID),
13661412
})
13671413
}
13681414

13691415
sourceName := "$apikey_auth_token"
1370-
mapName := fmt.Sprintf("apikey_auth_client_name_%s", strings.Split(strings.Replace(polKey, "-", "_", -1), "/")[1])
13711416

1372-
shaToClient := &version2.Map{
1417+
return &version2.Map{
13731418
Source: sourceName,
13741419
Variable: fmt.Sprintf("$%s", mapName),
13751420
Parameters: params,
13761421
}
1377-
1378-
p.APIKey = &version2.APIKey{
1379-
Header: apiKey.SuppliedIn.Header,
1380-
Query: apiKey.SuppliedIn.Query,
1381-
RejectCodeNotSupplied: generateIntFromPointer(apiKey.RejectCodes.NotSupplied, 401),
1382-
RejectCodeNoMatch: generateIntFromPointer(apiKey.RejectCodes.NoMatch, 403),
1383-
Clients: clients,
1384-
MapName: mapName,
1385-
}
1386-
return res, shaToClient
13871422
}
13881423

13891424
func (p *policiesCfg) addWAFConfig(
@@ -1474,9 +1509,8 @@ func (vsc *virtualServerConfigurator) generatePolicies(
14741509
policies map[string]*conf_v1.Policy,
14751510
context string,
14761511
policyOpts policyOptions,
1477-
) (policiesCfg, []version2.Map) {
1512+
) policiesCfg {
14781513
config := newPoliciesConfig(vsc.bundleValidator)
1479-
maps := []version2.Map{}
14801514

14811515
for _, p := range policyRefs {
14821516
polNamespace := p.Namespace
@@ -1518,44 +1552,27 @@ func (vsc *virtualServerConfigurator) generatePolicies(
15181552
case pol.Spec.OIDC != nil:
15191553
res = config.addOIDCConfig(pol.Spec.OIDC, key, polNamespace, policyOpts.secretRefs, vsc.oidcPolCfg)
15201554
case pol.Spec.APIKey != nil:
1521-
res, shaToClientMap := config.addAPIKeyConfig(pol.Spec.APIKey, key, polNamespace, policyOpts.secretRefs, context)
1522-
// TODO: refactor
1523-
if res != nil && len(res.warnings) > 0 {
1524-
vsc.addWarnings(ownerDetails.owner, res.warnings)
1525-
}
1526-
1527-
if res != nil && res.isError {
1528-
return policiesCfg{
1529-
ErrorReturn: &version2.Return{Code: 500},
1530-
}, maps
1531-
}
1532-
if res != nil && !res.isError && shaToClientMap != nil {
1533-
maps = append(maps, *shaToClientMap)
1534-
}
1535-
1555+
res = config.addAPIKeyConfig(pol.Spec.APIKey, key, polNamespace, policyOpts.secretRefs, context)
15361556
case pol.Spec.WAF != nil:
15371557
res = config.addWAFConfig(pol.Spec.WAF, key, polNamespace, policyOpts.apResources)
15381558
default:
15391559
res = newValidationResults()
15401560
}
1541-
if res != nil && len(res.warnings) > 0 {
1542-
vsc.addWarnings(ownerDetails.owner, res.warnings)
1543-
}
1544-
1545-
if res != nil && res.isError {
1561+
vsc.addWarnings(ownerDetails.owner, res.warnings)
1562+
if res.isError {
15461563
return policiesCfg{
15471564
ErrorReturn: &version2.Return{Code: 500},
1548-
}, maps
1565+
}
15491566
}
15501567
} else {
15511568
vsc.addWarningf(ownerDetails.owner, "Policy %s is missing or invalid", key)
15521569
return policiesCfg{
15531570
ErrorReturn: &version2.Return{Code: 500},
1554-
}, maps
1571+
}
15551572
}
15561573
}
15571574

1558-
return *config, maps
1575+
return *config
15591576
}
15601577

15611578
func generateLimitReq(zoneName string, rateLimitPol *conf_v1.RateLimit) version2.LimitReq {

0 commit comments

Comments
 (0)