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/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc
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/cockroachdb/cockroach-go/v2 v2.0.5
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.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo=
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.1/go.mod h1:mnuSyFmv+WSuk2C/zps445xiMU9dW384/pV4WnIS8b0=
github.com/caos/oidc v0.7.2 h1:tt6acMhNIhnlU+BhiNMYY00uhNL0vniiEwVKpWh8SMs=
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/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 (
"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
@ -46,8 +47,13 @@ func VerifyTokenAndWriteCtxData(ctx context.Context, token, orgID string, t *Tok
if err != nil {
return nil, err
}
projectID, err := t.GetProjectIDByClientID(ctx, clientID)
logging.LogWithFields("AUTH-GfAoV", "clientID", clientID).OnError(err).Warn("could not read projectid by clientid")
projectID, origins, err := t.ProjectIDAndOriginsByClientID(ctx, 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
}
@ -69,3 +75,14 @@ func GetAllPermissionsFromCtx(ctx context.Context) []string {
ctxPermission, _ := ctx.Value(allPermissionsKey).([]string)
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
}
func (v *testVerifier) ProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
return "", nil
func (v *testVerifier) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (string, []string, error) {
return "", nil, nil
}
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)
VerifierClientID(ctx context.Context, name string) (clientID string, 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
}
@ -88,8 +88,8 @@ func (v *TokenVerifier) ResolveGrant(ctx context.Context) (*Grant, error) {
return v.authZRepo.ResolveGrants(ctx)
}
func (v *TokenVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
return v.authZRepo.ProjectIDByClientID(ctx, clientID)
func (v *TokenVerifier) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (string, []string, error) {
return v.authZRepo.ProjectIDAndOriginsByClientID(ctx, clientID)
}
func (v *TokenVerifier) ExistsOrg(ctx context.Context, orgID string) error {

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"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)
}
func GetGatewayHeader(ctx context.Context, headername string) string {
return GetHeader(ctx, runtime.MetadataPrefix+headername)
}
func GetAuthorizationHeader(ctx context.Context) string {
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 {
handler = interceptor.GatewayHTTPInterceptor(handler)
}
return http_mw.CORSInterceptorOpts(http_mw.DefaultCORSOptions, handler)
return http_mw.CORSInterceptor(handler)
}
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) {
return nil, nil
}
func (v *verifierMock) ProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
return "", nil
func (v *verifierMock) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (string, []string, error) {
return "", nil, nil
}
func (v *verifierMock) ExistsOrg(ctx context.Context, orgID string) error {
return nil

View File

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

View File

@ -18,6 +18,8 @@ var (
http_utils.AcceptLanguage,
http_utils.Authorization,
http_utils.ZitadelOrgID,
http_utils.XUserAgent,
http_utils.XGrpcWeb,
},
AllowedMethods: []string{
http.MethodOptions,
@ -30,9 +32,10 @@ var (
},
ExposedHeaders: []string{
http_utils.Location,
http_utils.ContentLength,
},
AllowedOrigins: []string{
"http://localhost:*",
AllowOriginFunc: func(_ string) bool {
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/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/errors"
proj_model "github.com/caos/zitadel/internal/project/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)
}
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)
if err != nil {
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)
}

View File

@ -2,12 +2,13 @@ package eventstore
import (
"context"
"time"
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/crypto"
caos_errs "github.com/caos/zitadel/internal/errors"
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
"time"
)
type TokenVerifierRepo struct {
@ -40,12 +41,12 @@ func (repo *TokenVerifierRepo) VerifyAccessToken(ctx context.Context, tokenStrin
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)
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 {

View File

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

View File

@ -2,13 +2,16 @@ package model
import (
"encoding/json"
"time"
"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"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
"github.com/lib/pq"
"time"
)
const (
@ -39,6 +42,7 @@ type ApplicationView struct {
NoneCompliant bool `json:"-" gorm:"column:none_compliant"`
ComplianceProblems pq.StringArray `json:"-" gorm:"column:compliance_problems"`
DevMode bool `json:"devMode" gorm:"column:dev_mode"`
OriginAllowList pq.StringArray `json:"-" gorm:"column:origin_allow_list"`
Sequence uint64 `json:"-" gorm:"sequence"`
}
@ -62,6 +66,7 @@ func ApplicationViewFromModel(app *model.ApplicationView) *ApplicationView {
OIDCAuthMethodType: int32(app.OIDCAuthMethodType),
OIDCPostLogoutRedirectUris: app.OIDCPostLogoutRedirectUris,
DevMode: app.DevMode,
OriginAllowList: app.OriginAllowList,
}
}
@ -103,6 +108,7 @@ func ApplicationViewToModel(app *ApplicationView) *model.ApplicationView {
NoneCompliant: app.NoneCompliant,
ComplianceProblems: app.ComplianceProblems,
DevMode: app.DevMode,
OriginAllowList: app.OriginAllowList,
}
}
@ -171,6 +177,7 @@ func (a *ApplicationView) AppendEvent(event *models.Event) (err error) {
return err
}
a.setCompliance()
return a.setOriginAllowList()
case es_model.OIDCConfigChanged,
es_model.ApplicationChanged:
err = a.SetData(event)
@ -178,6 +185,7 @@ func (a *ApplicationView) AppendEvent(event *models.Event) (err error) {
return err
}
a.setCompliance()
return a.setOriginAllowList()
case es_model.ApplicationDeactivated:
a.State = int32(model.AppStateInactive)
case es_model.ApplicationReactivated:
@ -200,6 +208,21 @@ func (a *ApplicationView) SetData(event *models.Event) error {
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() {
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

View File

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

View File

@ -1,6 +1,7 @@
Errors:
Internal: An internal error occured
NoChangesFound: No changes found
NoChangesFound: No changes
OriginNotAllowed: This "Origin" is not allowed
User:
NotFound: User could not be found
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;