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:
Caleb Doxsey 2021-01-11 15:40:08 -07:00 committed by GitHub
parent 1466f4e5a0
commit a6bc9f492f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 328 additions and 162 deletions

View file

@ -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

View file

@ -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"},

View file

@ -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

View file

@ -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

View 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()
}
}

View file

@ -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)

View file

@ -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",