mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-03 08:50:42 +02:00
authorize: move impersonation into session/service account (#1765)
* move impersonation into session/service account * replace frontend statik * fix data race * move JWT filling to separate function, break up functions * maybe fix data race * fix code climate issue
This commit is contained in:
parent
1466f4e5a0
commit
a6bc9f492f
16 changed files with 328 additions and 162 deletions
|
@ -10,7 +10,6 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
@ -25,8 +24,6 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/session"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/user"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -180,46 +177,7 @@ func (e *Evaluator) JWTPayload(req *Request) map[string]interface{} {
|
|||
payload := map[string]interface{}{
|
||||
"iss": e.authenticateHost,
|
||||
}
|
||||
if u, err := url.Parse(req.HTTP.URL); err == nil {
|
||||
payload["aud"] = u.Hostname()
|
||||
}
|
||||
if s, ok := req.DataBrokerData.Get("type.googleapis.com/session.Session", req.Session.ID).(*session.Session); ok {
|
||||
payload["jti"] = s.GetId()
|
||||
if tm, err := ptypes.Timestamp(s.GetIdToken().GetExpiresAt()); err == nil {
|
||||
payload["exp"] = tm.Unix()
|
||||
}
|
||||
if tm, err := ptypes.Timestamp(s.GetIdToken().GetIssuedAt()); err == nil {
|
||||
payload["iat"] = tm.Unix()
|
||||
}
|
||||
if u, ok := req.DataBrokerData.Get("type.googleapis.com/user.User", s.GetUserId()).(*user.User); ok {
|
||||
payload["sub"] = u.GetId()
|
||||
payload["user"] = u.GetId()
|
||||
payload["email"] = u.GetEmail()
|
||||
}
|
||||
if du, ok := req.DataBrokerData.Get("type.googleapis.com/directory.User", s.GetUserId()).(*directory.User); ok {
|
||||
if du.GetEmail() != "" {
|
||||
payload["email"] = du.GetEmail()
|
||||
}
|
||||
var groupNames []string
|
||||
for _, groupID := range du.GetGroupIds() {
|
||||
if dg, ok := req.DataBrokerData.Get("type.googleapis.com/directory.Group", groupID).(*directory.Group); ok {
|
||||
groupNames = append(groupNames, dg.Name)
|
||||
}
|
||||
}
|
||||
var groups []string
|
||||
groups = append(groups, du.GetGroupIds()...)
|
||||
groups = append(groups, groupNames...)
|
||||
payload["groups"] = groups
|
||||
}
|
||||
}
|
||||
|
||||
if req.Session.ImpersonateEmail != "" {
|
||||
payload["email"] = req.Session.ImpersonateEmail
|
||||
}
|
||||
if len(req.Session.ImpersonateGroups) > 0 {
|
||||
payload["groups"] = req.Session.ImpersonateGroups
|
||||
}
|
||||
|
||||
req.fillJWTPayload(payload)
|
||||
return payload
|
||||
}
|
||||
|
||||
|
@ -305,10 +263,18 @@ func (e *Evaluator) newInput(req *Request, isValidClientCertificate bool) *input
|
|||
if i.DataBrokerData.Session == nil {
|
||||
i.DataBrokerData.Session = req.DataBrokerData.Get(serviceAccountTypeURL, req.Session.ID)
|
||||
}
|
||||
if obj, ok := i.DataBrokerData.Session.(interface{ GetUserId() string }); ok {
|
||||
i.DataBrokerData.User = req.DataBrokerData.Get(userTypeURL, obj.GetUserId())
|
||||
var userIDs []string
|
||||
if obj, ok := i.DataBrokerData.Session.(interface{ GetUserId() string }); ok && obj.GetUserId() != "" {
|
||||
userIDs = append(userIDs, obj.GetUserId())
|
||||
}
|
||||
if obj, ok := i.DataBrokerData.Session.(interface{ GetImpersonateUserId() string }); ok && obj.GetImpersonateUserId() != "" {
|
||||
userIDs = append(userIDs, obj.GetImpersonateUserId())
|
||||
}
|
||||
|
||||
user, ok := req.DataBrokerData.Get(directoryUserTypeURL, obj.GetUserId()).(*directory.User)
|
||||
for _, userID := range userIDs {
|
||||
i.DataBrokerData.User = req.DataBrokerData.Get(userTypeURL, userID)
|
||||
|
||||
user, ok := req.DataBrokerData.Get(directoryUserTypeURL, userID).(*directory.User)
|
||||
if ok {
|
||||
var groups []string
|
||||
for _, groupID := range user.GetGroupIds() {
|
||||
|
@ -331,31 +297,6 @@ func (e *Evaluator) newInput(req *Request, isValidClientCertificate bool) *input
|
|||
return i
|
||||
}
|
||||
|
||||
type (
|
||||
// Request is the request data used for the evaluator.
|
||||
Request struct {
|
||||
DataBrokerData DataBrokerData `json:"databroker_data"`
|
||||
HTTP RequestHTTP `json:"http"`
|
||||
Session RequestSession `json:"session"`
|
||||
CustomPolicies []string
|
||||
}
|
||||
|
||||
// RequestHTTP is the HTTP field in the request.
|
||||
RequestHTTP struct {
|
||||
Method string `json:"method"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
ClientCertificate string `json:"client_certificate"`
|
||||
}
|
||||
|
||||
// RequestSession is the session field in the request.
|
||||
RequestSession struct {
|
||||
ID string `json:"id"`
|
||||
ImpersonateEmail string `json:"impersonate_email"`
|
||||
ImpersonateGroups []string `json:"impersonate_groups"`
|
||||
}
|
||||
)
|
||||
|
||||
// Result is the result of evaluation.
|
||||
type Result struct {
|
||||
Status int
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
|
@ -58,9 +59,7 @@ func TestJSONMarshal(t *testing.T) {
|
|||
ClientCertificate: "CLIENT_CERTIFICATE",
|
||||
},
|
||||
Session: RequestSession{
|
||||
ID: "SESSION_ID",
|
||||
ImpersonateEmail: "y@example.com",
|
||||
ImpersonateGroups: []string{"group1"},
|
||||
ID: "SESSION_ID",
|
||||
},
|
||||
}, true))
|
||||
assert.JSONEq(t, `{
|
||||
|
@ -79,9 +78,7 @@ func TestJSONMarshal(t *testing.T) {
|
|||
"url": "https://example.com"
|
||||
},
|
||||
"session": {
|
||||
"id": "SESSION_ID",
|
||||
"impersonate_email": "y@example.com",
|
||||
"impersonate_groups": ["group1"]
|
||||
"id": "SESSION_ID"
|
||||
},
|
||||
"is_valid_client_certificate": true
|
||||
}`, string(bs))
|
||||
|
@ -158,6 +155,7 @@ func TestEvaluator_JWTPayload(t *testing.T) {
|
|||
ExpiresAt: nowPb,
|
||||
IssuedAt: nowPb,
|
||||
},
|
||||
ExpiresAt: nowPb,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -174,6 +172,31 @@ func TestEvaluator_JWTPayload(t *testing.T) {
|
|||
"iat": now.Unix(),
|
||||
},
|
||||
},
|
||||
{
|
||||
"with service account",
|
||||
&Request{
|
||||
DataBrokerData: DataBrokerData{
|
||||
"type.googleapis.com/user.ServiceAccount": map[string]interface{}{
|
||||
"SERVICE_ACCOUNT_ID": &user.ServiceAccount{
|
||||
Id: "SERVICE_ACCOUNT_ID",
|
||||
IssuedAt: nowPb,
|
||||
ExpiresAt: nowPb,
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP: RequestHTTP{URL: "https://example.com"},
|
||||
Session: RequestSession{
|
||||
ID: "SERVICE_ACCOUNT_ID",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"iss": "authn.example.com",
|
||||
"jti": "SERVICE_ACCOUNT_ID",
|
||||
"aud": "example.com",
|
||||
"exp": now.Unix(),
|
||||
"iat": now.Unix(),
|
||||
},
|
||||
},
|
||||
{
|
||||
"with user",
|
||||
&Request{
|
||||
|
@ -252,12 +275,22 @@ func TestEvaluator_JWTPayload(t *testing.T) {
|
|||
&Request{
|
||||
HTTP: RequestHTTP{URL: "https://example.com"},
|
||||
Session: RequestSession{
|
||||
ImpersonateEmail: "user@example.com",
|
||||
ImpersonateGroups: []string{"admin", "test"},
|
||||
ID: "SESSION_ID",
|
||||
},
|
||||
DataBrokerData: DataBrokerData{
|
||||
"type.googleapis.com/session.Session": map[string]interface{}{
|
||||
"SESSION_ID": &session.Session{
|
||||
Id: "SESSION_ID",
|
||||
UserId: "USER_ID",
|
||||
ImpersonateEmail: proto.String("user@example.com"),
|
||||
ImpersonateGroups: []string{"admin", "test"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"iss": "authn.example.com",
|
||||
"jti": "SESSION_ID",
|
||||
"aud": "example.com",
|
||||
"email": "user@example.com",
|
||||
"groups": []string{"admin", "test"},
|
||||
|
|
|
@ -14,7 +14,7 @@ all_allowed_groups := get_allowed_groups(route_policy)
|
|||
all_allowed_users := get_allowed_users(route_policy)
|
||||
all_allowed_idp_claims := get_allowed_idp_claims(route_policy)
|
||||
|
||||
is_impersonating := count(input.session.impersonate_email) > 0
|
||||
is_impersonating := count(session.impersonate_email) > 0
|
||||
|
||||
# allow public
|
||||
allow {
|
||||
|
@ -52,14 +52,14 @@ allow {
|
|||
# allow by impersonate email
|
||||
allow {
|
||||
is_impersonating
|
||||
all_allowed_users[_] = input.session.impersonate_email
|
||||
all_allowed_users[_] = session.impersonate_email
|
||||
}
|
||||
|
||||
# allow by impersonate group
|
||||
allow {
|
||||
is_impersonating
|
||||
some group
|
||||
input.session.impersonate_groups[_] = group
|
||||
session.impersonate_groups[_] = group
|
||||
all_allowed_groups[_] = group
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ allow {
|
|||
allow {
|
||||
is_impersonating
|
||||
some domain
|
||||
email_in_domain(input.session.impersonate_email, all_allowed_domains[domain])
|
||||
email_in_domain(session.impersonate_email, all_allowed_domains[domain])
|
||||
}
|
||||
|
||||
# allow by arbitrary idp claims
|
||||
|
|
|
@ -15,7 +15,7 @@ test_email_allowed {
|
|||
}
|
||||
} with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "" }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
|
||||
test_impersonate_email_not_allowed {
|
||||
|
@ -26,14 +26,14 @@ test_impersonate_email_not_allowed {
|
|||
}] with
|
||||
input.databroker_data as {
|
||||
"session": {
|
||||
"user_id": "user1"
|
||||
"user_id": "user1", "impersonate_email": "y@example.com"
|
||||
},
|
||||
"user": {
|
||||
"email": "x@example.com"
|
||||
}
|
||||
} with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "y@example.com" }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
|
||||
test_impersonate_email_allowed {
|
||||
|
@ -44,14 +44,14 @@ test_impersonate_email_allowed {
|
|||
}] with
|
||||
input.databroker_data as {
|
||||
"session": {
|
||||
"user_id": "user1"
|
||||
"user_id": "user1", "impersonate_email": "y@example.com"
|
||||
},
|
||||
"user": {
|
||||
"email": "x@example.com"
|
||||
}
|
||||
} with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "y@example.com" }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
|
||||
test_group_allowed {
|
||||
|
@ -81,7 +81,7 @@ test_impersonate_groups_not_allowed {
|
|||
}] with
|
||||
input.databroker_data as {
|
||||
"session": {
|
||||
"user_id": "user1"
|
||||
"user_id": "user1", "impersonate_email": "y@example.com", "impersonate_groups": ["2"]
|
||||
},
|
||||
"user": {
|
||||
"email": "x@example.com"
|
||||
|
@ -89,7 +89,7 @@ test_impersonate_groups_not_allowed {
|
|||
"groups": ["1"]
|
||||
} with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "y@example.com", "impersonate_groups": ["2"] }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
|
||||
test_impersonate_groups_allowed {
|
||||
|
@ -100,7 +100,7 @@ test_impersonate_groups_allowed {
|
|||
}] with
|
||||
input.databroker_data as {
|
||||
"session": {
|
||||
"user_id": "user1"
|
||||
"user_id": "user1", "impersonate_email": "y@example.com", "impersonate_groups": ["2"]
|
||||
},
|
||||
"user": {
|
||||
"email": "x@example.com"
|
||||
|
@ -110,7 +110,7 @@ test_impersonate_groups_allowed {
|
|||
}
|
||||
} with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "y@example.com", "impersonate_groups": ["2"] }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
|
||||
test_domain_allowed {
|
||||
|
@ -121,14 +121,14 @@ test_domain_allowed {
|
|||
}] with
|
||||
input.databroker_data as {
|
||||
"session": {
|
||||
"user_id": "user1"
|
||||
"user_id": "user1", "impersonate_email": ""
|
||||
},
|
||||
"user": {
|
||||
"email": "x@example.com"
|
||||
}
|
||||
} with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "" }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
|
||||
test_impersonate_domain_not_allowed {
|
||||
|
@ -139,14 +139,14 @@ test_impersonate_domain_not_allowed {
|
|||
}] with
|
||||
input.databroker_data as {
|
||||
"session": {
|
||||
"user_id": "user1"
|
||||
"user_id": "user1", "impersonate_email": "y@example1.com"
|
||||
},
|
||||
"user": {
|
||||
"email": "x@example.com"
|
||||
}
|
||||
} with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "y@example1.com" }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
|
||||
test_impersonate_domain_allowed {
|
||||
|
@ -157,14 +157,14 @@ test_impersonate_domain_allowed {
|
|||
}] with
|
||||
input.databroker_data as {
|
||||
"session": {
|
||||
"user_id": "user1"
|
||||
"user_id": "user1", "impersonate_email": "y@example1.com"
|
||||
},
|
||||
"user": {
|
||||
"email": "x@example.com"
|
||||
}
|
||||
} with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "y@example1.com" }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
|
||||
test_idp_claims_allowed {
|
||||
|
@ -183,7 +183,7 @@ test_idp_claims_allowed {
|
|||
}
|
||||
} with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "" }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
|
||||
test_example {
|
||||
|
@ -395,7 +395,7 @@ test_any_authenticated_user_allowed {
|
|||
}
|
||||
} with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "" }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
test_any_authenticated_user_denied {
|
||||
not allow with
|
||||
|
@ -404,5 +404,5 @@ test_any_authenticated_user_denied {
|
|||
"AllowAnyAuthenticatedUser": true
|
||||
}] with
|
||||
input.http as { "url": "http://example.com" } with
|
||||
input.session as { "id": "session1", "impersonate_email": "" }
|
||||
input.session as { "id": "session1" }
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
100
authorize/evaluator/request.go
Normal file
100
authorize/evaluator/request.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
package evaluator
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/directory"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/session"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/user"
|
||||
)
|
||||
|
||||
type (
|
||||
// Request is the request data used for the evaluator.
|
||||
Request struct {
|
||||
DataBrokerData DataBrokerData `json:"databroker_data"`
|
||||
HTTP RequestHTTP `json:"http"`
|
||||
Session RequestSession `json:"session"`
|
||||
CustomPolicies []string
|
||||
}
|
||||
|
||||
// RequestHTTP is the HTTP field in the request.
|
||||
RequestHTTP struct {
|
||||
Method string `json:"method"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
ClientCertificate string `json:"client_certificate"`
|
||||
}
|
||||
|
||||
// RequestSession is the session field in the request.
|
||||
RequestSession struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
)
|
||||
|
||||
type sessionOrServiceAccount interface {
|
||||
GetId() string
|
||||
GetExpiresAt() *timestamppb.Timestamp
|
||||
GetIssuedAt() *timestamppb.Timestamp
|
||||
GetUserId() string
|
||||
GetImpersonateEmail() string
|
||||
GetImpersonateGroups() []string
|
||||
GetImpersonateUserId() string
|
||||
}
|
||||
|
||||
func (req *Request) fillJWTPayload(payload map[string]interface{}) {
|
||||
if u, err := url.Parse(req.HTTP.URL); err == nil {
|
||||
payload["aud"] = u.Hostname()
|
||||
}
|
||||
|
||||
if s, ok := req.DataBrokerData.Get("type.googleapis.com/session.Session", req.Session.ID).(*session.Session); ok {
|
||||
req.fillJWTPayloadSessionOrServiceAccount(payload, s)
|
||||
}
|
||||
|
||||
if sa, ok := req.DataBrokerData.Get("type.googleapis.com/user.ServiceAccount", req.Session.ID).(*user.ServiceAccount); ok {
|
||||
req.fillJWTPayloadSessionOrServiceAccount(payload, sa)
|
||||
}
|
||||
}
|
||||
|
||||
func (req *Request) fillJWTPayloadSessionOrServiceAccount(payload map[string]interface{}, s sessionOrServiceAccount) {
|
||||
payload["jti"] = s.GetId()
|
||||
if s.GetExpiresAt().IsValid() {
|
||||
payload["exp"] = s.GetExpiresAt().AsTime().Unix()
|
||||
}
|
||||
if s.GetIssuedAt().IsValid() {
|
||||
payload["iat"] = s.GetIssuedAt().AsTime().Unix()
|
||||
}
|
||||
|
||||
userID := s.GetUserId()
|
||||
if s.GetImpersonateUserId() != "" {
|
||||
userID = s.GetImpersonateUserId()
|
||||
}
|
||||
if u, ok := req.DataBrokerData.Get("type.googleapis.com/user.User", userID).(*user.User); ok {
|
||||
payload["sub"] = u.GetId()
|
||||
payload["user"] = u.GetId()
|
||||
payload["email"] = u.GetEmail()
|
||||
}
|
||||
if du, ok := req.DataBrokerData.Get("type.googleapis.com/directory.User", userID).(*directory.User); ok {
|
||||
if du.GetEmail() != "" {
|
||||
payload["email"] = du.GetEmail()
|
||||
}
|
||||
var groupNames []string
|
||||
for _, groupID := range du.GetGroupIds() {
|
||||
if dg, ok := req.DataBrokerData.Get("type.googleapis.com/directory.Group", groupID).(*directory.Group); ok {
|
||||
groupNames = append(groupNames, dg.Name)
|
||||
}
|
||||
}
|
||||
var groups []string
|
||||
groups = append(groups, du.GetGroupIds()...)
|
||||
groups = append(groups, groupNames...)
|
||||
payload["groups"] = groups
|
||||
}
|
||||
|
||||
if s.GetImpersonateEmail() != "" {
|
||||
payload["email"] = s.GetImpersonateEmail()
|
||||
}
|
||||
if len(s.GetImpersonateGroups()) > 0 {
|
||||
payload["groups"] = s.GetImpersonateGroups()
|
||||
}
|
||||
}
|
|
@ -230,9 +230,7 @@ func (a *Authorize) getEvaluatorRequestFromCheckRequest(in *envoy_service_auth_v
|
|||
}
|
||||
if sessionState != nil {
|
||||
req.Session = evaluator.RequestSession{
|
||||
ID: sessionState.ID,
|
||||
ImpersonateEmail: sessionState.ImpersonateEmail,
|
||||
ImpersonateGroups: sessionState.ImpersonateGroups,
|
||||
ID: sessionState.ID,
|
||||
}
|
||||
}
|
||||
p := a.getMatchingPolicy(requestURL)
|
||||
|
|
|
@ -92,9 +92,7 @@ func Test_getEvaluatorRequest(t *testing.T) {
|
|||
)
|
||||
expect := &evaluator.Request{
|
||||
Session: evaluator.RequestSession{
|
||||
ID: "SESSION_ID",
|
||||
ImpersonateEmail: "foo@example.com",
|
||||
ImpersonateGroups: []string{"admin", "test"},
|
||||
ID: "SESSION_ID",
|
||||
},
|
||||
HTTP: evaluator.RequestHTTP{
|
||||
Method: "GET",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue