fix: cors (#621)

* fix: dont (re)generate client secret with auth type none

* fix(cors): allow Origin from request

* feat: add origin allow list and fix some core issues

* rename migration

* fix UserIDsByDomain

* check origin on userinfo

* update oidc pkg
This commit is contained in:
Livio Amstutz 2020-08-24 10:06:55 +02:00 committed by GitHub
parent 193cfb45f6
commit 4e1e8a714a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 128 additions and 26 deletions

2
go.mod
View File

@ -16,7 +16,7 @@ require (
github.com/aws/aws-sdk-go v1.33.13 // indirect github.com/aws/aws-sdk-go v1.33.13 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc
github.com/caos/logging v0.0.2 github.com/caos/logging v0.0.2
github.com/caos/oidc v0.7.1 github.com/caos/oidc v0.7.2
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cockroachdb/cockroach-go/v2 v2.0.5 github.com/cockroachdb/cockroach-go/v2 v2.0.5
github.com/envoyproxy/protoc-gen-validate v0.4.0 github.com/envoyproxy/protoc-gen-validate v0.4.0

4
go.sum
View File

@ -71,8 +71,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBW
github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0= github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo= github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo=
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0= github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/oidc v0.7.1 h1:/Snk5rM43h98kh4S57etnqfherH/o98O0djQxnY+9y0= github.com/caos/oidc v0.7.2 h1:tt6acMhNIhnlU+BhiNMYY00uhNL0vniiEwVKpWh8SMs=
github.com/caos/oidc v0.7.1/go.mod h1:mnuSyFmv+WSuk2C/zps445xiMU9dW384/pV4WnIS8b0= github.com/caos/oidc v0.7.2/go.mod h1:mnuSyFmv+WSuk2C/zps445xiMU9dW384/pV4WnIS8b0=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

View File

@ -2,9 +2,10 @@ package authz
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/logging" "github.com/caos/zitadel/internal/api/grpc"
http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/errors"
) )
type key int type key int
@ -46,8 +47,13 @@ func VerifyTokenAndWriteCtxData(ctx context.Context, token, orgID string, t *Tok
if err != nil { if err != nil {
return nil, err return nil, err
} }
projectID, err := t.GetProjectIDByClientID(ctx, clientID) projectID, origins, err := t.ProjectIDAndOriginsByClientID(ctx, clientID)
logging.LogWithFields("AUTH-GfAoV", "clientID", clientID).OnError(err).Warn("could not read projectid by clientid") if err != nil {
return nil, errors.ThrowPermissionDenied(err, "AUTH-GHpw2", "could not read projectid by clientid")
}
if err := checkOrigin(ctx, origins); err != nil {
return nil, err
}
return context.WithValue(ctx, dataKey, CtxData{UserID: userID, OrgID: orgID, ProjectID: projectID, AgentID: agentID}), nil return context.WithValue(ctx, dataKey, CtxData{UserID: userID, OrgID: orgID, ProjectID: projectID, AgentID: agentID}), nil
} }
@ -69,3 +75,14 @@ func GetAllPermissionsFromCtx(ctx context.Context) []string {
ctxPermission, _ := ctx.Value(allPermissionsKey).([]string) ctxPermission, _ := ctx.Value(allPermissionsKey).([]string)
return ctxPermission return ctxPermission
} }
func checkOrigin(ctx context.Context, origins []string) error {
origin := grpc.GetGatewayHeader(ctx, http_util.Origin)
if origin == "" {
return nil
}
if http_util.IsOriginAllowed(origins, origin) {
return nil
}
return errors.ThrowPermissionDenied(nil, "AUTH-DZG21", "Errors.OriginNotAllowed")
}

View File

@ -23,8 +23,8 @@ func (v *testVerifier) ResolveGrants(ctx context.Context) (*Grant, error) {
return v.grant, nil return v.grant, nil
} }
func (v *testVerifier) ProjectIDByClientID(ctx context.Context, clientID string) (string, error) { func (v *testVerifier) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (string, []string, error) {
return "", nil return "", nil, nil
} }
func (v *testVerifier) ExistsOrg(ctx context.Context, orgID string) error { func (v *testVerifier) ExistsOrg(ctx context.Context, orgID string) error {

View File

@ -22,7 +22,7 @@ type authZRepo interface {
VerifyAccessToken(ctx context.Context, token, clientID string) (userID, agentID string, err error) VerifyAccessToken(ctx context.Context, token, clientID string) (userID, agentID string, err error)
VerifierClientID(ctx context.Context, name string) (clientID string, err error) VerifierClientID(ctx context.Context, name string) (clientID string, err error)
ResolveGrants(ctx context.Context) (grant *Grant, err error) ResolveGrants(ctx context.Context) (grant *Grant, err error)
ProjectIDByClientID(ctx context.Context, clientID string) (projectID string, err error) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (projectID string, origins []string, err error)
ExistsOrg(ctx context.Context, orgID string) error ExistsOrg(ctx context.Context, orgID string) error
} }
@ -88,8 +88,8 @@ func (v *TokenVerifier) ResolveGrant(ctx context.Context) (*Grant, error) {
return v.authZRepo.ResolveGrants(ctx) return v.authZRepo.ResolveGrants(ctx)
} }
func (v *TokenVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) { func (v *TokenVerifier) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (string, []string, error) {
return v.authZRepo.ProjectIDByClientID(ctx, clientID) return v.authZRepo.ProjectIDAndOriginsByClientID(ctx, clientID)
} }
func (v *TokenVerifier) ExistsOrg(ctx context.Context, orgID string) error { func (v *TokenVerifier) ExistsOrg(ctx context.Context, orgID string) error {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/caos/zitadel/internal/api/http" "github.com/caos/zitadel/internal/api/http"
) )
@ -12,6 +13,10 @@ func GetHeader(ctx context.Context, headername string) string {
return metautils.ExtractIncoming(ctx).Get(headername) return metautils.ExtractIncoming(ctx).Get(headername)
} }
func GetGatewayHeader(ctx context.Context, headername string) string {
return GetHeader(ctx, runtime.MetadataPrefix+headername)
}
func GetAuthorizationHeader(ctx context.Context) string { func GetAuthorizationHeader(ctx context.Context) string {
return GetHeader(ctx, http.Authorization) return GetHeader(ctx, http.Authorization)
} }

View File

@ -131,7 +131,7 @@ func addInterceptors(handler http.Handler, g Gateway) http.Handler {
if interceptor, ok := g.(grpcGatewayCustomInterceptor); ok { if interceptor, ok := g.(grpcGatewayCustomInterceptor); ok {
handler = interceptor.GatewayHTTPInterceptor(handler) handler = interceptor.GatewayHTTPInterceptor(handler)
} }
return http_mw.CORSInterceptorOpts(http_mw.DefaultCORSOptions, handler) return http_mw.CORSInterceptor(handler)
} }
func gatewayPort(port string) string { func gatewayPort(port string) string {

View File

@ -27,8 +27,8 @@ func (v *verifierMock) VerifyAccessToken(ctx context.Context, token, clientID st
func (v *verifierMock) ResolveGrants(ctx context.Context) (*authz.Grant, error) { func (v *verifierMock) ResolveGrants(ctx context.Context) (*authz.Grant, error) {
return nil, nil return nil, nil
} }
func (v *verifierMock) ProjectIDByClientID(ctx context.Context, clientID string) (string, error) { func (v *verifierMock) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (string, []string, error) {
return "", nil return "", nil, nil
} }
func (v *verifierMock) ExistsOrg(ctx context.Context, orgID string) error { func (v *verifierMock) ExistsOrg(ctx context.Context, orgID string) error {
return nil return nil

View File

@ -13,12 +13,15 @@ const (
AcceptLanguage = "accept-language" AcceptLanguage = "accept-language"
CacheControl = "cache-control" CacheControl = "cache-control"
ContentType = "content-type" ContentType = "content-type"
ContentLength = "content-length"
Expires = "expires" Expires = "expires"
Location = "location" Location = "location"
Origin = "origin" Origin = "origin"
Pragma = "pragma" Pragma = "pragma"
UserAgentHeader = "user-agent" UserAgentHeader = "user-agent"
ForwardedFor = "x-forwarded-for" ForwardedFor = "x-forwarded-for"
XUserAgent = "x-user-agent"
XGrpcWeb = "x-grpc-web"
ContentSecurityPolicy = "content-security-policy" ContentSecurityPolicy = "content-security-policy"
XXSSProtection = "x-xss-protection" XXSSProtection = "x-xss-protection"

View File

@ -18,6 +18,8 @@ var (
http_utils.AcceptLanguage, http_utils.AcceptLanguage,
http_utils.Authorization, http_utils.Authorization,
http_utils.ZitadelOrgID, http_utils.ZitadelOrgID,
http_utils.XUserAgent,
http_utils.XGrpcWeb,
}, },
AllowedMethods: []string{ AllowedMethods: []string{
http.MethodOptions, http.MethodOptions,
@ -30,9 +32,10 @@ var (
}, },
ExposedHeaders: []string{ ExposedHeaders: []string{
http_utils.Location, http_utils.Location,
http_utils.ContentLength,
}, },
AllowedOrigins: []string{ AllowOriginFunc: func(_ string) bool {
"http://localhost:*", return true
}, },
} }
) )

View File

@ -0,0 +1,23 @@
package http
import (
"fmt"
"net/url"
)
func GetOriginFromURLString(s string) (string, error) {
parsed, err := url.Parse(s)
if err != nil {
return "", err
}
return fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host), nil
}
func IsOriginAllowed(allowList []string, origin string) bool {
for _, allowed := range allowList {
if allowed == origin {
return true
}
}
return false
}

View File

@ -7,6 +7,7 @@ import (
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/pkg/op"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
proj_model "github.com/caos/zitadel/internal/project/model" proj_model "github.com/caos/zitadel/internal/project/model"
user_model "github.com/caos/zitadel/internal/user/model" user_model "github.com/caos/zitadel/internal/user/model"
@ -41,11 +42,18 @@ func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secr
return o.repo.AuthorizeOIDCApplication(ctx, id, secret) return o.repo.AuthorizeOIDCApplication(ctx, id, secret)
} }
func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID string) (*oidc.Userinfo, error) { func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID, origin string) (*oidc.Userinfo, error) {
token, err := o.repo.TokenByID(ctx, tokenID) token, err := o.repo.TokenByID(ctx, tokenID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
app, err := o.repo.ApplicationByClientID(ctx, token.ApplicationID)
if err != nil {
return nil, err
}
if origin != "" && !http.IsOriginAllowed(app.OriginAllowList, origin) {
return nil, errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
}
return o.GetUserinfoFromScopes(ctx, token.UserID, token.Scopes) return o.GetUserinfoFromScopes(ctx, token.UserID, token.Scopes)
} }

View File

@ -2,12 +2,13 @@ package eventstore
import ( import (
"context" "context"
"time"
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/view" "github.com/caos/zitadel/internal/authz/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing" iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing" proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
"time"
) )
type TokenVerifierRepo struct { type TokenVerifierRepo struct {
@ -40,12 +41,12 @@ func (repo *TokenVerifierRepo) VerifyAccessToken(ctx context.Context, tokenStrin
return "", "", caos_errs.ThrowUnauthenticated(nil, "APP-Zxfako", "invalid audience") return "", "", caos_errs.ThrowUnauthenticated(nil, "APP-Zxfako", "invalid audience")
} }
func (repo *TokenVerifierRepo) ProjectIDByClientID(ctx context.Context, clientID string) (projectID string, err error) { func (repo *TokenVerifierRepo) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (projectID string, origins []string, err error) {
app, err := repo.View.ApplicationByOIDCClientID(clientID) app, err := repo.View.ApplicationByOIDCClientID(clientID)
if err != nil { if err != nil {
return "", err return "", nil, err
} }
return app.ProjectID, nil return app.ProjectID, app.OriginAllowList, nil
} }
func (repo *TokenVerifierRepo) ExistsOrg(ctx context.Context, orgID string) error { func (repo *TokenVerifierRepo) ExistsOrg(ctx context.Context, orgID string) error {

View File

@ -1,8 +1,9 @@
package model package model
import ( import (
"github.com/caos/zitadel/internal/model"
"time" "time"
"github.com/caos/zitadel/internal/model"
) )
type ApplicationView struct { type ApplicationView struct {
@ -25,6 +26,7 @@ type ApplicationView struct {
NoneCompliant bool NoneCompliant bool
ComplianceProblems []string ComplianceProblems []string
DevMode bool DevMode bool
OriginAllowList []string
Sequence uint64 Sequence uint64
} }

View File

@ -2,13 +2,16 @@ package model
import ( import (
"encoding/json" "encoding/json"
"time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/lib/pq"
http_util "github.com/caos/zitadel/internal/api/http"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/model"
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
"github.com/lib/pq"
"time"
) )
const ( const (
@ -39,6 +42,7 @@ type ApplicationView struct {
NoneCompliant bool `json:"-" gorm:"column:none_compliant"` NoneCompliant bool `json:"-" gorm:"column:none_compliant"`
ComplianceProblems pq.StringArray `json:"-" gorm:"column:compliance_problems"` ComplianceProblems pq.StringArray `json:"-" gorm:"column:compliance_problems"`
DevMode bool `json:"devMode" gorm:"column:dev_mode"` DevMode bool `json:"devMode" gorm:"column:dev_mode"`
OriginAllowList pq.StringArray `json:"-" gorm:"column:origin_allow_list"`
Sequence uint64 `json:"-" gorm:"sequence"` Sequence uint64 `json:"-" gorm:"sequence"`
} }
@ -62,6 +66,7 @@ func ApplicationViewFromModel(app *model.ApplicationView) *ApplicationView {
OIDCAuthMethodType: int32(app.OIDCAuthMethodType), OIDCAuthMethodType: int32(app.OIDCAuthMethodType),
OIDCPostLogoutRedirectUris: app.OIDCPostLogoutRedirectUris, OIDCPostLogoutRedirectUris: app.OIDCPostLogoutRedirectUris,
DevMode: app.DevMode, DevMode: app.DevMode,
OriginAllowList: app.OriginAllowList,
} }
} }
@ -103,6 +108,7 @@ func ApplicationViewToModel(app *ApplicationView) *model.ApplicationView {
NoneCompliant: app.NoneCompliant, NoneCompliant: app.NoneCompliant,
ComplianceProblems: app.ComplianceProblems, ComplianceProblems: app.ComplianceProblems,
DevMode: app.DevMode, DevMode: app.DevMode,
OriginAllowList: app.OriginAllowList,
} }
} }
@ -171,6 +177,7 @@ func (a *ApplicationView) AppendEvent(event *models.Event) (err error) {
return err return err
} }
a.setCompliance() a.setCompliance()
return a.setOriginAllowList()
case es_model.OIDCConfigChanged, case es_model.OIDCConfigChanged,
es_model.ApplicationChanged: es_model.ApplicationChanged:
err = a.SetData(event) err = a.SetData(event)
@ -178,6 +185,7 @@ func (a *ApplicationView) AppendEvent(event *models.Event) (err error) {
return err return err
} }
a.setCompliance() a.setCompliance()
return a.setOriginAllowList()
case es_model.ApplicationDeactivated: case es_model.ApplicationDeactivated:
a.State = int32(model.AppStateInactive) a.State = int32(model.AppStateInactive)
case es_model.ApplicationReactivated: case es_model.ApplicationReactivated:
@ -200,6 +208,21 @@ func (a *ApplicationView) SetData(event *models.Event) error {
return nil return nil
} }
func (a *ApplicationView) setOriginAllowList() error {
allowList := make([]string, 0)
for _, redirect := range a.OIDCRedirectUris {
origin, err := http_util.GetOriginFromURLString(redirect)
if err != nil {
return err
}
if !http_util.IsOriginAllowed(allowList, origin) {
allowList = append(allowList, origin)
}
}
a.OriginAllowList = allowList
return nil
}
func (a *ApplicationView) setCompliance() { func (a *ApplicationView) setCompliance() {
compliance := model.GetOIDCCompliance(model.OIDCVersion(a.OIDCVersion), model.OIDCApplicationType(a.OIDCApplicationType), OIDCGrantTypesToModel(a.OIDCGrantTypes), OIDCResponseTypesToModel(a.OIDCResponseTypes), model.OIDCAuthMethodType(a.OIDCAuthMethodType), a.OIDCRedirectUris) compliance := model.GetOIDCCompliance(model.OIDCVersion(a.OIDCVersion), model.OIDCApplicationType(a.OIDCApplicationType), OIDCGrantTypesToModel(a.OIDCGrantTypes), OIDCResponseTypesToModel(a.OIDCResponseTypes), model.OIDCAuthMethodType(a.OIDCAuthMethodType), a.OIDCRedirectUris)
a.NoneCompliant = compliance.NoneCompliant a.NoneCompliant = compliance.NoneCompliant

View File

@ -1,6 +1,7 @@
Errors: Errors:
Internal: Es ist ein interner Fehler aufgetreten Internal: Es ist ein interner Fehler aufgetreten
NoChangesFound: Keine Änderungen gefunden NoChangesFound: Keine Änderungen gefunden
OriginNotAllowed: Dieser "Origin" ist nicht freigeschaltet
User: User:
NotFound: Benutzer konnte nicht gefunden werden NotFound: Benutzer konnte nicht gefunden werden
UserIDMissing: User ID fehlt UserIDMissing: User ID fehlt

View File

@ -1,6 +1,7 @@
Errors: Errors:
Internal: An internal error occured Internal: An internal error occured
NoChangesFound: No changes found NoChangesFound: No changes
OriginNotAllowed: This "Origin" is not allowed
User: User:
NotFound: User could not be found NotFound: User could not be found
UserIDMissing: User ID missing UserIDMissing: User ID missing

View File

@ -0,0 +1,15 @@
BEGIN;
ALTER TABLE management.applications ADD COLUMN origin_allow_list TEXT ARRAY;
ALTER TABLE auth.applications ADD COLUMN origin_allow_list TEXT ARRAY;
ALTER TABLE authz.applications ADD COLUMN origin_allow_list TEXT ARRAY;
TRUNCATE TABLE management.applications;
TRUNCATE TABLE auth.applications;
TRUNCATE TABLE authz.applications;
UPDATE management.current_sequences set current_sequence = 0 where view_name = 'management.applications';
UPDATE auth.current_sequences set current_sequence = 0 where view_name = 'auth.applications';
UPDATE authz.current_sequences set current_sequence = 0 where view_name = 'authz.applications';
COMMIT;