From c280119498787dac0de06a19626da45ab356970a Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Thu, 27 Feb 2025 13:39:28 -0700 Subject: [PATCH] policy: support emails from directory user (#5504) --- config/policy_ppl_test.go | 44 ++++++++++++++++++++++-------- pkg/policy/criteria/domain.go | 6 +++- pkg/policy/criteria/domain_test.go | 22 +++++++++++++++ pkg/policy/criteria/email.go | 6 +++- pkg/policy/criteria/email_test.go | 22 +++++++++++++++ pkg/policy/rules/rules.go | 21 ++++++++++++-- 6 files changed, 106 insertions(+), 15 deletions(-) diff --git a/config/policy_ppl_test.go b/config/policy_ppl_test.go index 1e1bcac3c..cbc0c458a 100644 --- a/config/policy_ppl_test.go +++ b/config/policy_ppl_test.go @@ -85,7 +85,8 @@ else := [false, {"user-unauthenticated"}] domain_0 := [true, {"domain-ok"}] if { session := get_session(input.session.id) user := get_user(session) - domain := split(get_user_email(session, user), "@")[1] + directory_user := get_directory_user(session) + domain := split(get_user_email(session, user, directory_user), "@")[1] domain == "a.example.com" } @@ -99,7 +100,8 @@ else := [false, {"user-unauthenticated"}] domain_1 := [true, {"domain-ok"}] if { session := get_session(input.session.id) user := get_user(session) - domain := split(get_user_email(session, user), "@")[1] + directory_user := get_directory_user(session) + domain := split(get_user_email(session, user, directory_user), "@")[1] domain == "b.example.com" } @@ -113,7 +115,8 @@ else := [false, {"user-unauthenticated"}] domain_2 := [true, {"domain-ok"}] if { session := get_session(input.session.id) user := get_user(session) - domain := split(get_user_email(session, user), "@")[1] + directory_user := get_directory_user(session) + domain := split(get_user_email(session, user, directory_user), "@")[1] domain == "c.example.com" } @@ -127,7 +130,8 @@ else := [false, {"user-unauthenticated"}] domain_3 := [true, {"domain-ok"}] if { session := get_session(input.session.id) user := get_user(session) - domain := split(get_user_email(session, user), "@")[1] + directory_user := get_directory_user(session) + domain := split(get_user_email(session, user, directory_user), "@")[1] domain == "d.example.com" } @@ -141,7 +145,8 @@ else := [false, {"user-unauthenticated"}] domain_4 := [true, {"domain-ok"}] if { session := get_session(input.session.id) user := get_user(session) - domain := split(get_user_email(session, user), "@")[1] + directory_user := get_directory_user(session) + domain := split(get_user_email(session, user, directory_user), "@")[1] domain == "e.example.com" } @@ -240,7 +245,8 @@ else := [false, {"user-unauthenticated"}] email_0 := [true, {"email-ok"}] if { session := get_session(input.session.id) user := get_user(session) - email := get_user_email(session, user) + directory_user := get_directory_user(session) + email := get_user_email(session, user, directory_user) email == "user1" } @@ -267,7 +273,8 @@ else := [false, {"user-unauthenticated"}] email_1 := [true, {"email-ok"}] if { session := get_session(input.session.id) user := get_user(session) - email := get_user_email(session, user) + directory_user := get_directory_user(session) + email := get_user_email(session, user, directory_user) email == "user2" } @@ -294,7 +301,8 @@ else := [false, {"user-unauthenticated"}] email_2 := [true, {"email-ok"}] if { session := get_session(input.session.id) user := get_user(session) - email := get_user_email(session, user) + directory_user := get_directory_user(session) + email := get_user_email(session, user, directory_user) email == "user3" } @@ -321,7 +329,8 @@ else := [false, {"user-unauthenticated"}] email_3 := [true, {"email-ok"}] if { session := get_session(input.session.id) user := get_user(session) - email := get_user_email(session, user) + directory_user := get_directory_user(session) + email := get_user_email(session, user, directory_user) email == "user4" } @@ -348,7 +357,8 @@ else := [false, {"user-unauthenticated"}] email_4 := [true, {"email-ok"}] if { session := get_session(input.session.id) user := get_user(session) - email := get_user_email(session, user) + directory_user := get_directory_user(session) + email := get_user_email(session, user, directory_user) email == "user5" } @@ -478,7 +488,19 @@ get_user(session) := v if { else := {} -get_user_email(session, user) := v if { +get_directory_user(session) := v if { + v = get_databroker_record("pomerium.io/DirectoryUser", session.user_id) + v != null +} + +else := {} + +get_user_email(session, user, directory_user) := v if { + v = object.get(directory_user, "email", "") + v != "" +} + +else := v if { v = user.email } diff --git a/pkg/policy/criteria/domain.go b/pkg/policy/criteria/domain.go index da8bb7b9f..625838f62 100644 --- a/pkg/policy/criteria/domain.go +++ b/pkg/policy/criteria/domain.go @@ -15,7 +15,10 @@ var domainBody = ast.Body{ user := get_user(session) `), ast.MustParseExpr(` - domain := split(get_user_email(session, user), "@")[1] + directory_user := get_directory_user(session) + `), + ast.MustParseExpr(` + domain := split(get_user_email(session, user, directory_user), "@")[1] `), } @@ -48,6 +51,7 @@ func (c domainCriterion) GenerateRule(_ string, data parser.Value) (*ast.Rule, [ rules.GetSession(), rules.GetUser(), rules.GetUserEmail(), + rules.GetDirectoryUser(), }, nil } diff --git a/pkg/policy/criteria/domain_test.go b/pkg/policy/criteria/domain_test.go index f8cf68d6d..dd1c7eda2 100644 --- a/pkg/policy/criteria/domain_test.go +++ b/pkg/policy/criteria/domain_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/pomerium/datasource/pkg/directory" "github.com/pomerium/pomerium/pkg/grpc/databroker" "github.com/pomerium/pomerium/pkg/grpc/session" "github.com/pomerium/pomerium/pkg/grpc/user" @@ -66,4 +67,25 @@ allow: require.Equal(t, A{true, A{ReasonDomainOK}, M{}}, res["allow"]) require.Equal(t, A{false, A{}}, res["deny"]) }) + t.Run("by directory user", func(t *testing.T) { + res, err := evaluate(t, ` +allow: + and: + - domain: + is: example.com +`, + []*databroker.Record{ + makeRecord(&session.Session{ + Id: "SESSION_ID", + UserId: "USER_ID", + }), + makeStructRecord(directory.UserRecordType, "USER_ID", map[string]any{ + "email": "user@example.com", + }), + }, + Input{Session: InputSession{ID: "SESSION_ID"}}) + require.NoError(t, err) + require.Equal(t, A{true, A{ReasonDomainOK}, M{}}, res["allow"]) + require.Equal(t, A{false, A{}}, res["deny"]) + }) } diff --git a/pkg/policy/criteria/email.go b/pkg/policy/criteria/email.go index f3cc72743..a0755feea 100644 --- a/pkg/policy/criteria/email.go +++ b/pkg/policy/criteria/email.go @@ -16,7 +16,10 @@ var emailBody = ast.Body{ user := get_user(session) `), ast.MustParseExpr(` - email := get_user_email(session, user) + directory_user := get_directory_user(session) + `), + ast.MustParseExpr(` + email := get_user_email(session, user, directory_user) `), } @@ -49,6 +52,7 @@ func (c emailCriterion) GenerateRule(_ string, data parser.Value) (*ast.Rule, [] rules.GetSession(), rules.GetUser(), rules.GetUserEmail(), + rules.GetDirectoryUser(), }, nil } diff --git a/pkg/policy/criteria/email_test.go b/pkg/policy/criteria/email_test.go index 613d27a82..4443eae8e 100644 --- a/pkg/policy/criteria/email_test.go +++ b/pkg/policy/criteria/email_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" + "github.com/pomerium/datasource/pkg/directory" "github.com/pomerium/pomerium/pkg/grpc/databroker" "github.com/pomerium/pomerium/pkg/grpc/session" "github.com/pomerium/pomerium/pkg/grpc/user" @@ -76,4 +77,25 @@ allow: require.Equal(t, A{true, A{ReasonEmailOK}, M{}}, res["allow"]) require.Equal(t, A{false, A{}}, res["deny"]) }) + t.Run("by directory user", func(t *testing.T) { + res, err := evaluate(t, ` +allow: + and: + - email: + is: test@example.com +`, + []*databroker.Record{ + makeRecord(&session.Session{ + Id: "SESSION_ID", + UserId: "USER_ID", + }), + makeStructRecord(directory.UserRecordType, "USER_ID", map[string]any{ + "email": "test@example.com", + }), + }, + Input{Session: InputSession{ID: "SESSION_ID"}}) + require.NoError(t, err) + require.Equal(t, A{true, A{ReasonEmailOK}, M{}}, res["allow"]) + require.Equal(t, A{false, A{}}, res["deny"]) + }) } diff --git a/pkg/policy/rules/rules.go b/pkg/policy/rules/rules.go index a43db38f7..986536ad8 100644 --- a/pkg/policy/rules/rules.go +++ b/pkg/policy/rules/rules.go @@ -1,7 +1,11 @@ // Package rules contains useful pre-defined rego AST rules. package rules -import "github.com/open-policy-agent/opa/ast" +import ( + "github.com/open-policy-agent/opa/ast" + + "github.com/pomerium/datasource/pkg/directory" +) // GetSession gets the session for the given id. func GetSession() *ast.Rule { @@ -24,6 +28,16 @@ get_session(id) := v if { `) } +// GetDirectoryUser returns the directory user for the given session. +func GetDirectoryUser() *ast.Rule { + return MustParse(` +get_directory_user(session) := v if { + v = get_databroker_record("` + directory.UserRecordType + `", session.user_id) + v != null +} else := {} +`) +} + // GetUser returns the user for the given session. func GetUser() *ast.Rule { return MustParse(` @@ -37,7 +51,10 @@ get_user(session) := v if { // GetUserEmail gets the user email, either the impersonate email, or the user email. func GetUserEmail() *ast.Rule { return MustParse(` -get_user_email(session, user) := v if { +get_user_email(session, user, directory_user) := v if { + v = object.get(directory_user, "email", "") + v != "" +} else := v if { v = user.email } else := "" `)