Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"_format_version": "3.0",
"services": [
{
"host": "example.com",
"id": "730d612d-914b-5fe8-8ead-e6aa654318ef",
"name": "example",
"path": "/",
"plugins": [],
"port": 443,
"protocol": "https",
"routes": [
{
"id": "52dbb730-8d56-578c-bc69-0543a34bff4a",
"methods": [
"GET"
],
"name": "example_getoidc",
"paths": [
"~/oidc$"
],
"plugins": [
{
"config": {
"issuer": "https://example.com/.well-known/openid-configuration",
"scopes_required": []
},
"name": "openid-connect"
}
],
"regex_priority": 200,
"strip_path": false,
"tags": [
"OAS3_import",
"OAS3file_38-ignore-security-errors-oidc.yaml"
]
},
{
"id": "9e0937d1-93db-53ba-8ec8-4034f983557e",
"methods": [
"GET"
],
"name": "example_getoidcscopes",
"paths": [
"~/oidc-with-scopes$"
],
"plugins": [
{
"config": {
"issuer": "https://example.com/.well-known/openid-configuration",
"scopes_required": [
"read",
"write"
]
},
"name": "openid-connect"
}
],
"regex_priority": 200,
"strip_path": false,
"tags": [
"OAS3_import",
"OAS3file_38-ignore-security-errors-oidc.yaml"
]
}
],
"tags": [
"OAS3_import",
"OAS3file_38-ignore-security-errors-oidc.yaml"
]
}
],
"upstreams": []
}

33 changes: 33 additions & 0 deletions openapi2kong/oas3_testfiles/38-ignore-security-errors-oidc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Regression test for https://github.com/Kong/deck/issues/1829
# When --ignore-security-errors is used with a valid single openIdConnect scheme,
# the openid-connect plugin should still be generated.

openapi: 3.0.4
info:
title: example
version: 1.2.0
servers:
- url: https://example.com
paths:
/oidc:
get:
operationId: getOidc
security:
- OpenIDConnect: []
responses:
'204':
description: No content
/oidc-with-scopes:
get:
operationId: getOidcScopes
security:
- OpenIDConnect: ["read", "write"]
responses:
'200':
description: OK
components:
securitySchemes:
OpenIDConnect:
type: openIdConnect
openIdConnectUrl: https://example.com/.well-known/openid-configuration

21 changes: 14 additions & 7 deletions openapi2kong/openapi2kong.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,24 +162,31 @@ func getOIDCdefaults(
scheme *v3.SecurityScheme // the security-scheme object
)
{
if len(requirements) == 0 || ignoreSecurityErrors {
// no security requirements or nothing is defined
if len(requirements) == 0 {
// no security requirements defined
// so return inherited (can be nil)
return inherited, nil
}

if len(requirements) > 1 && !ignoreSecurityErrors {
return nil, fmt.Errorf("only a single security-requirement is supported")
if len(requirements) > 1 {
// multiple requirements represent OR logic, which is not supported
if !ignoreSecurityErrors {
return nil, fmt.Errorf("only a single security-requirement is supported")
}
return inherited, nil
}

requirement := requirements[0].Requirements
if requirement.Len() == 0 || ignoreSecurityErrors {
if requirement.Len() == 0 {
return inherited, nil // there is nothing defined, so return inherited (can be nil)
}

if requirement.Len() > 1 && !ignoreSecurityErrors {
if requirement.Len() > 1 {
// multiple schemes are a logical AND, which is not supported
return nil, fmt.Errorf("within a security-requirement only a single security-scheme is supported")
if !ignoreSecurityErrors {
return nil, fmt.Errorf("within a security-requirement only a single security-scheme is supported")
}
return inherited, nil
}

// requirement has only 1 entry
Expand Down
186 changes: 186 additions & 0 deletions openapi2kong/openapi2kong_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,189 @@ func Test_Openapi2kong_SkipRouteByHeader(t *testing.T) {
}
}
}

// Test_Openapi2kong_IgnoreSecurityErrors verifies that --ignore-security-errors
// does not suppress OIDC plugin generation for valid single openIdConnect schemes.
// Regression test for https://github.com/Kong/deck/issues/1829
func Test_Openapi2kong_IgnoreSecurityErrors(t *testing.T) {
suffix := ".expected.json"
testFiles := []string{
"38-ignore-security-errors-oidc",
}

for _, fileNameBase := range testFiles {
t.Run(fileNameBase, func(t *testing.T) {
fileNameIn := fileNameBase + ".yaml"
fileNameExpected := fileNameBase + suffix
fileNameOut := fileNameBase + ".generated.json"

dataIn, _ := os.ReadFile(fixturePath + fileNameIn)
dataOut, err := Convert(dataIn, O2kOptions{
Tags: []string{"OAS3_import", "OAS3file_" + fileNameIn},
OIDC: true,
IgnoreSecurityErrors: true,
})

if err != nil {
t.Errorf("'%s' didn't expect error: %v", fixturePath+fileNameIn, err)
} else {
JSONOut, _ := json.MarshalIndent(dataOut, "", " ")
os.WriteFile(fixturePath+fileNameOut, JSONOut, 0o600)
JSONExpected, _ := os.ReadFile(fixturePath + fileNameExpected)
assert.JSONEq(t, string(JSONExpected), string(JSONOut),
"'%s': the JSON blobs should be equal", fixturePath+fileNameIn)
}
})
}
}

// Test_Openapi2kong_IgnoreSecurityErrors_SkipsInvalid verifies that --ignore-security-errors
// suppresses errors for unsupported security configurations (multiple requirements, multiple schemes,
// non-OIDC types) but still processes valid ones in the same document.
func Test_Openapi2kong_IgnoreSecurityErrors_SkipsInvalid(t *testing.T) {
tests := []struct {
name string
spec string
wantOIDC bool
}{
{
name: "multiple requirements at doc level are ignored",
spec: `
openapi: "3.0.0"
info:
title: test
version: v1
servers:
- url: https://example.com
security:
- oidc1: []
- oidc2: []
paths:
/test:
get:
operationId: getTest
responses:
"200":
description: OK
components:
securitySchemes:
oidc1:
type: openIdConnect
openIdConnectUrl: https://example.com/.well-known/openid-configuration
oidc2:
type: openIdConnect
openIdConnectUrl: https://example.com/.well-known/openid-configuration
`,
wantOIDC: false,
},
{
name: "non-OIDC type at operation level is ignored, no plugin generated",
spec: `
openapi: "3.0.0"
info:
title: test
version: v1
servers:
- url: https://example.com
paths:
/test:
get:
operationId: getTest
security:
- apiKey: []
responses:
"200":
description: OK
components:
securitySchemes:
apiKey:
type: apiKey
in: header
name: X-API-Key
`,
wantOIDC: false,
},
{
name: "valid single OIDC at operation level generates plugin",
spec: `
openapi: "3.0.0"
info:
title: test
version: v1
servers:
- url: https://example.com
paths:
/test:
get:
operationId: getTest
security:
- oidc: []
responses:
"200":
description: OK
components:
securitySchemes:
oidc:
type: openIdConnect
openIdConnectUrl: https://example.com/.well-known/openid-configuration
`,
wantOIDC: true,
},
{
name: "mixed supported OIDC and unsupported apiKey in one spec",
spec: `
openapi: "3.0.4"
info:
title: mixed-security
version: "1.0.0"
servers:
- url: https://example.com
components:
securitySchemes:
OpenIDConnect:
type: openIdConnect
openIdConnectUrl: https://auth.example.com/.well-known/openid-configuration
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
paths:
/secure-endpoint:
get:
operationId: secureEndpoint
security:
- OpenIDConnect: []
responses:
"204":
description: No content
/legacy-endpoint:
get:
operationId: legacyEndpoint
security:
- ApiKeyAuth: []
responses:
"204":
description: No content
`,
wantOIDC: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result, err := Convert([]byte(tc.spec), O2kOptions{
OIDC: true,
IgnoreSecurityErrors: true,
})
assert.NoError(t, err)

jsonData, _ := json.Marshal(result)
hasOIDC := strings.Contains(string(jsonData), `"name":"openid-connect"`)
if tc.wantOIDC {
assert.True(t, hasOIDC, "expected openid-connect plugin to be generated")
} else {
assert.False(t, hasOIDC, "expected no openid-connect plugin to be generated")
}
})
}
}
Loading