// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package apitests import ( "net/url" "testing" "time" capOIDC "github.com/hashicorp/cap/oidc" "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/ci" "github.com/shoenig/test/must" ) func TestACLOIDC_GetAuthURL(t *testing.T) { ci.Parallel(t) testClient, testServer, _ := makeACLClient(t, nil, nil) defer testServer.Stop() // Set up the test OIDC provider. oidcTestProvider := capOIDC.StartTestProvider(t) defer oidcTestProvider.Stop() oidcTestProvider.SetAllowedRedirectURIs([]string{"http://127.0.0.1:4649/oidc/callback"}) // Generate and upsert an ACL auth method for use. Certain values must be // taken from the cap OIDC provider just like real world use. mockedAuthMethod := api.ACLAuthMethod{ Name: "api-test-auth-method", Type: api.ACLAuthMethodTypeOIDC, TokenLocality: api.ACLAuthMethodTokenLocalityGlobal, MaxTokenTTL: 10 * time.Hour, Default: true, Config: &api.ACLAuthMethodConfig{ OIDCDiscoveryURL: oidcTestProvider.Addr(), OIDCClientID: "mock", OIDCClientSecret: "verysecretsecret", BoundAudiences: []string{"mock"}, AllowedRedirectURIs: []string{"http://127.0.0.1:4649/oidc/callback"}, DiscoveryCaPem: []string{oidcTestProvider.CACert()}, SigningAlgs: []string{"ES256"}, ClaimMappings: map[string]string{"foo": "bar"}, ListClaimMappings: map[string]string{"foo": "bar"}, }, } createdAuthMethod, writeMeta, err := testClient.ACLAuthMethods().Create(&mockedAuthMethod, nil) must.NoError(t, err) must.NotNil(t, createdAuthMethod) assertWriteMeta(t, writeMeta) // Generate and make the request. authURLRequest := api.ACLOIDCAuthURLRequest{ AuthMethodName: createdAuthMethod.Name, RedirectURI: createdAuthMethod.Config.AllowedRedirectURIs[0], ClientNonce: "fpSPuaodKevKfDU3IeXb", } authURLResp, _, err := testClient.ACLAuth().GetAuthURL(&authURLRequest, nil) must.NoError(t, err) // The response URL comes encoded, so decode this and check we have each // component we expect. escapedURL, err := url.PathUnescape(authURLResp.AuthURL) must.NoError(t, err) must.StrContains(t, escapedURL, "/authorize?client_id=mock") must.StrContains(t, escapedURL, "&nonce=fpSPuaodKevKfDU3IeXb") must.StrContains(t, escapedURL, "&redirect_uri=http://127.0.0.1:4649/oidc/callback") must.StrContains(t, escapedURL, "&response_type=code") must.StrContains(t, escapedURL, "&scope=openid") must.StrContains(t, escapedURL, "&state=st_") } func TestACLOIDC_CompleteAuth(t *testing.T) { ci.Parallel(t) testClient, testServer, _ := makeACLClient(t, nil, nil) defer testServer.Stop() // Set up the test OIDC provider. oidcTestProvider := capOIDC.StartTestProvider(t) defer oidcTestProvider.Stop() oidcTestProvider.SetAllowedRedirectURIs([]string{"http://127.0.0.1:4649/oidc/callback"}) // Generate and upsert an ACL auth method for use. Certain values must be // taken from the cap OIDC provider just like real world use. mockedAuthMethod := api.ACLAuthMethod{ Name: "api-test-auth-method", Type: api.ACLAuthMethodTypeOIDC, TokenLocality: api.ACLAuthMethodTokenLocalityGlobal, MaxTokenTTL: 10 * time.Hour, Default: true, Config: &api.ACLAuthMethodConfig{ OIDCDiscoveryURL: oidcTestProvider.Addr(), OIDCClientID: "mock", OIDCClientSecret: "verysecretsecret", BoundAudiences: []string{"mock"}, AllowedRedirectURIs: []string{"http://127.0.0.1:4649/oidc/callback"}, DiscoveryCaPem: []string{oidcTestProvider.CACert()}, SigningAlgs: []string{"ES256"}, ClaimMappings: map[string]string{}, ListClaimMappings: map[string]string{ "http://nomad.internal/roles": "roles", "http://nomad.internal/policies": "policies", }, }, } createdAuthMethod, writeMeta, err := testClient.ACLAuthMethods().Create(&mockedAuthMethod, nil) must.NoError(t, err) must.NotNil(t, createdAuthMethod) assertWriteMeta(t, writeMeta) // Set our custom data and some expected values, so we can make the call // and use the test provider. oidcTestProvider.SetExpectedAuthNonce("fpSPuaodKevKfDU3IeXb") oidcTestProvider.SetExpectedAuthCode("codeABC") oidcTestProvider.SetCustomAudience("mock") oidcTestProvider.SetExpectedState("st_someweirdstateid") oidcTestProvider.SetCustomClaims(map[string]interface{}{ "azp": "mock", "http://nomad.internal/policies": []string{"engineering"}, "http://nomad.internal/roles": []string{"engineering"}, }) // Upsert an ACL policy and role, so that we can reference this within our // OIDC claims. mockedACLPolicy := api.ACLPolicy{ Name: "api-oidc-login-test", Rules: `namespace "default" { policy = "write"}`, } _, err = testClient.ACLPolicies().Upsert(&mockedACLPolicy, nil) must.NoError(t, err) mockedACLRole := api.ACLRole{ Name: "api-oidc-login-test", Policies: []*api.ACLRolePolicyLink{{Name: mockedACLPolicy.Name}}, } createRoleResp, _, err := testClient.ACLRoles().Create(&mockedACLRole, nil) must.NoError(t, err) must.NotNil(t, createRoleResp) // Generate and upsert two binding rules, so we can test both ACL Policy // and Role claim mapping. mockedBindingRule1 := api.ACLBindingRule{ AuthMethod: mockedAuthMethod.Name, Selector: "engineering in list.policies", BindType: api.ACLBindingRuleBindTypePolicy, BindName: mockedACLPolicy.Name, } createBindingRole1Resp, _, err := testClient.ACLBindingRules().Create(&mockedBindingRule1, nil) must.NoError(t, err) must.NotNil(t, createBindingRole1Resp) mockedBindingRule2 := api.ACLBindingRule{ AuthMethod: mockedAuthMethod.Name, Selector: "engineering in list.roles", BindType: api.ACLBindingRuleBindTypeRole, BindName: mockedACLRole.Name, } createBindingRole2Resp, _, err := testClient.ACLBindingRules().Create(&mockedBindingRule2, nil) must.NoError(t, err) must.NotNil(t, createBindingRole2Resp) // Generate and make the request. authURLRequest := api.ACLOIDCCompleteAuthRequest{ AuthMethodName: createdAuthMethod.Name, RedirectURI: createdAuthMethod.Config.AllowedRedirectURIs[0], ClientNonce: "fpSPuaodKevKfDU3IeXb", State: "st_someweirdstateid", Code: "codeABC", } completeAuthResp, _, err := testClient.ACLAuth().CompleteAuth(&authURLRequest, nil) must.NoError(t, err) must.NotNil(t, completeAuthResp) must.Len(t, 1, completeAuthResp.Policies) must.Eq(t, mockedACLPolicy.Name, completeAuthResp.Policies[0]) must.Len(t, 1, completeAuthResp.Roles) must.Eq(t, mockedACLRole.Name, completeAuthResp.Roles[0].Name) must.Eq(t, createRoleResp.ID, completeAuthResp.Roles[0].ID) }