diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef22867b..922a170b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,11 +79,11 @@ Our automated PR checks verify that: To build OpenPubkey, ensure you have Go version `>= 1.20` installed. To verify which version you have installed, try `go version`. -To run the [Google example](https://github.com/openpubkey/openpubkey/tree/main/examples/google): - 1. Navigate to the `examples/google/` directory. +To run the [Simple example](https://github.com/openpubkey/openpubkey/tree/main/examples/simple): + 1. Navigate to the `examples/simple/` directory. 2. Execute `go build` - 3. Execute `./google login` to generate a valid PK token using Google as your OIDC provider. - 4. Execute `./google sign` to use the PK token generated in (3) to sign a verifiable message. + 3. Execute `./simple login` to generate a valid PK token using Google as your OIDC provider. + 4. Execute `./simple sign` to use the PK token generated in (3) to sign a verifiable message. # Contributing Roles diff --git a/policy/enforcer.go b/policy/enforcer.go index 9a896bce..f5dbcecc 100644 --- a/policy/enforcer.go +++ b/policy/enforcer.go @@ -25,6 +25,11 @@ import ( "golang.org/x/exp/slices" ) +const ( + OIDC_GROUPS = "oidc:groups:" + OIDC_WILDCARD_EMAIL = "oidc-match-end:email:" +) + // Enforcer evaluates opkssh policy to determine if the desired principal is // permitted type Enforcer struct { @@ -41,15 +46,24 @@ type checkedClaims struct { // Validates that the server defined identity attribute matches the // respective claim from the identity token func validateClaim(claims *checkedClaims, user *User) bool { - if strings.HasPrefix(user.IdentityAttribute, "oidc:groups") { + if strings.HasPrefix(claims.Email, OIDC_WILDCARD_EMAIL) { + return false + } + + if strings.HasPrefix(user.IdentityAttribute, OIDC_GROUPS) { oidcGroupSections := strings.Split(user.IdentityAttribute, ":") return slices.Contains(claims.Groups, oidcGroupSections[len(oidcGroupSections)-1]) } - + wildCardEmailMatch := false + if strings.HasPrefix(user.IdentityAttribute, OIDC_WILDCARD_EMAIL) { + if strings.HasSuffix(strings.ToLower(claims.Email), strings.ToLower(user.IdentityAttribute[len(OIDC_WILDCARD_EMAIL):len(user.IdentityAttribute)])) { + wildCardEmailMatch = true + } + } // email should be a case-insensitive check // sub should be a case-sensitive check - return strings.EqualFold(claims.Email, user.IdentityAttribute) || string(claims.Sub) == user.IdentityAttribute + return wildCardEmailMatch || strings.EqualFold(claims.Email, user.IdentityAttribute) || string(claims.Sub) == user.IdentityAttribute } // CheckPolicy loads the opkssh policy and checks to see if there is a policy diff --git a/policy/enforcer_test.go b/policy/enforcer_test.go index d05c934b..40fa3c5f 100644 --- a/policy/enforcer_test.go +++ b/policy/enforcer_test.go @@ -96,6 +96,21 @@ var policyTest = &policy.Policy{ Principals: []string{"test"}, Issuer: "https://accounts.example.com", }, + { + IdentityAttribute: "oidc-match-end:email:@wildcard.com", + Principals: []string{"test"}, + Issuer: "https://accounts.example.com", + }, + { + IdentityAttribute: "email@corp.com", + Principals: []string{"test"}, + Issuer: "http://127.0.0.1:8090/accounts", + }, + { + IdentityAttribute: "oidc-match-end:email:@[127.0.0.1]", + Principals: []string{"test"}, + Issuer: "http://127.0.0.1:8090/accounts", + }, }, } @@ -119,6 +134,18 @@ var policyWithOidcGroup = &policy.Policy{ Principals: []string{"test"}, Issuer: "https://accounts.example.com", }, + { + IdentityAttribute: "oidc-match-end:email:@example2.com", + Principals: []string{"test"}, + }, + { + IdentityAttribute: "oidc-match-end:email:oidc-match-end:email:@example.com", + Principals: []string{"test"}, + }, + { + IdentityAttribute: "”oidc-match-end:email:@”@example.com", + Principals: []string{"test"}, + }, }, } @@ -315,3 +342,61 @@ func TestPolicyDeniedMissingOidcGroupsClaim(t *testing.T) { err = policyEnforcer.CheckPolicy("test", pkt) require.Error(t, err, "user should not as the token is missing the groups claim") } + +func TestWildcardMatchEntry(t *testing.T) { + t.Parallel() + + op, _, err := NewMockOpenIdProvider2(false, "https://accounts.example.com", "test_client_wildcard", map[string]any{"email": "some.guy@wildcard.com"}) + require.NoError(t, err) + + opkClient, err := client.New(op) + require.NoError(t, err) + + pkt, err := opkClient.Auth(context.Background()) + require.NoError(t, err) + policyEnforcer := &policy.Enforcer{ + PolicyLoader: &MockPolicyLoader{Policy: policyTest}, + } + + // Check that policy file is properly parsed and checked + err = policyEnforcer.CheckPolicy("test", pkt) + require.NoError(t, err) +} + +func TestLocalProvider(t *testing.T) { + t.Parallel() + + op, _, err := NewMockOpenIdProvider2(false, "http://127.0.0.1:8090/accounts", "test_client_local_op", map[string]any{"email": "email@corp.com"}) + require.NoError(t, err) + + opkClient, err := client.New(op) + require.NoError(t, err) + pkt, err := opkClient.Auth(context.Background()) + require.NoError(t, err) + + policyEnforcer := &policy.Enforcer{ + PolicyLoader: &MockPolicyLoader{Policy: policyWithOidcGroup}, + } + + err = policyEnforcer.CheckPolicy("test", pkt) + require.NoError(t, err) +} + +func TestLocalEmail(t *testing.T) { + t.Parallel() + + op, _, err := NewMockOpenIdProvider2(false, "http://127.0.0.1:8090/accounts", "test_client_local_op", map[string]any{"email": "oidc-match-end:email:@[127.0.0.1]"}) + require.NoError(t, err) + + opkClient, err := client.New(op) + require.NoError(t, err) + + pkt, err := opkClient.Auth(context.Background()) + require.NoError(t, err) + policyEnforcer := &policy.Enforcer{ + PolicyLoader: &MockPolicyLoader{Policy: policyTest}, + } + // Check that policy file is properly parsed and checked + err = policyEnforcer.CheckPolicy("test", pkt) + require.NoError(t, err) +}