mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 21:07:22 +00:00
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:
parent
193cfb45f6
commit
4e1e8a714a
2
go.mod
2
go.mod
@ -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
4
go.sum
@ -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=
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
},
|
||||
}
|
||||
)
|
||||
|
23
internal/api/http/origin.go
Normal file
23
internal/api/http/origin.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
15
migrations/cockroach/V1.6__origin_allow_list.sql
Normal file
15
migrations/cockroach/V1.6__origin_allow_list.sql
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user