Merge branch 'main' into eventstore-created-at

This commit is contained in:
Livio Spring
2023-05-16 08:44:25 +02:00
committed by GitHub
17 changed files with 178 additions and 209 deletions

View File

@@ -7,6 +7,7 @@
/k8s/ /k8s/
/node_modules/ /node_modules/
/console/src/app/proto/generated/ /console/src/app/proto/generated/
/console/.angular
/console/tmp/ /console/tmp/
.releaserc.js .releaserc.js
changelog.config.js changelog.config.js
@@ -18,3 +19,4 @@ pkg/grpc/*/*.pb.*
pkg/grpc/*/*.swagger.json pkg/grpc/*/*.swagger.json
.goreleaser.yaml .goreleaser.yaml
.artifacts/ .artifacts/
.vscode

View File

@@ -80,7 +80,7 @@ jobs:
name: go-codecov name: go-codecov
- name: Bump Chart Version - name: Bump Chart Version
uses: peter-evans/repository-dispatch@v2 uses: peter-evans/repository-dispatch@v2
if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/main' if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/next'
with: with:
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
repository: zitadel/zitadel-charts repository: zitadel/zitadel-charts

View File

@@ -338,6 +338,7 @@ Please refer to the [README](./docs/README.md) for more information and local te
- **Code with variables**: Make sure that code snippets can be used by setting environment variables, instead of manually replacing a placeholder. - **Code with variables**: Make sure that code snippets can be used by setting environment variables, instead of manually replacing a placeholder.
- **Embedded files**: When embedding mdx files, make sure the template ist prefixed by "_" (lowdash). The content will be rendered inside the parent page, but is not accessible individually (eg, by search). - **Embedded files**: When embedding mdx files, make sure the template ist prefixed by "_" (lowdash). The content will be rendered inside the parent page, but is not accessible individually (eg, by search).
- **Don't repeat yourself**: When using the same content in multiple places, save and manage the content as separate file and make use of embedded files to import it into other docs pages.
- **Embedded code**: You can embed code snippets from a repository. See the [plugin](https://github.com/saucelabs/docusaurus-theme-github-codeblock#usage) for usage. - **Embedded code**: You can embed code snippets from a repository. See the [plugin](https://github.com/saucelabs/docusaurus-theme-github-codeblock#usage) for usage.
### Docs Pull Request ### Docs Pull Request

View File

@@ -306,9 +306,8 @@ func startAPIs(
http_util.WithNonHttpOnly(), http_util.WithNonHttpOnly(),
http_util.WithMaxAge(int(math.Floor(config.Quotas.Access.ExhaustedCookieMaxAge.Seconds()))), http_util.WithMaxAge(int(math.Floor(config.Quotas.Access.ExhaustedCookieMaxAge.Seconds()))),
) )
limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, config.Quotas.Access, false) limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, config.Quotas.Access)
nonLimitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, nil, config.Quotas.Access, true) apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, limitingAccessInterceptor)
apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, accessSvc, exhaustedCookieHandler, config.Quotas.Access)
if err != nil { if err != nil {
return fmt.Errorf("error creating api %w", err) return fmt.Errorf("error creating api %w", err)
} }
@@ -376,7 +375,7 @@ func startAPIs(
} }
apis.RegisterHandlerOnPrefix(saml.HandlerPrefix, samlProvider.HttpHandler()) apis.RegisterHandlerOnPrefix(saml.HandlerPrefix, samlProvider.HttpHandler())
c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, nonLimitingAccessInterceptor.Handle, config.CustomerPortal) c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, limitingAccessInterceptor, config.CustomerPortal)
if err != nil { if err != nil {
return fmt.Errorf("unable to start console: %w", err) return fmt.Errorf("unable to start console: %w", err)
} }

12
docs/.gitignore vendored
View File

@@ -9,6 +9,18 @@
.cache-loader .cache-loader
.artifacts .artifacts
# Generated by docusaurus-plugin-openapi-docs
docs/apis/auth
docs/apis/mgmt
docs/apis/admin
docs/apis/system
docs/apis/user_service
docs/apis/session_service
docs/apis/system
docs/apis/user_service
docs/apis/session_service
docs/apis/settings_service
# Misc # Misc
.DS_Store .DS_Store
.env.local .env.local

View File

@@ -1,27 +0,0 @@
---
title: Overview
---
import {ListElement, ListWrapper, ICONTYPE} from '../../src/components/list';
import Column from '../../src/components/column';
This section contains important agreements, policies and appendices relevant for users of our websites and services.
All documents will be provided in English language.
<Column>
<ListWrapper title="Main documents">
<ListElement link="/docs/legal/terms-of-service" type={ICONTYPE.POLICY} title="Terms of Service" description="" />
<ListElement link="/docs/legal/data-processing-agreement" type={ICONTYPE.POLICY} title="Data Processing Agreement" description="" />
<ListElement link="/docs/legal/privacy-policy" type={ICONTYPE.POLICY} title="Privacy Policy" description="" />
</ListWrapper>
<ListWrapper title="Service">
<ListElement link="/docs/legal/service-level-description" type={ICONTYPE.SERVICE} title="Service Level" description="Service levels offered by ZITADEL Cloud" />
<ListElement link="/docs/legal/cloud-service-description" type={ICONTYPE.SERVICE} title="Cloud Service" description="Service description and data location" />
<ListElement link="/docs/legal/terms-of-service-dedicated" type={ICONTYPE.SERVICE} title="Dedicated Instances" description="Terms and Conditions of dedicated Instances" />
</ListWrapper>
<ListWrapper title="Annexes">
<ListElement link="/docs/legal/support-services" type={ICONTYPE.POLICY} title="Support Services" description="Support services offered by ZITADEL and CAOS Ltd." />
<ListElement link="/docs/legal/acceptable-use-policy" type={ICONTYPE.POLICY} title="Acceptable Use Policy" description="Obligations while using ZITADEL Services" />
<ListElement link="/docs/legal/rate-limit-policy" type={ICONTYPE.POLICY} title="Rate Limit Policy" description="How ZITADEL will use rate limiting" />
</ListWrapper>
</Column>

View File

@@ -1,39 +0,0 @@
---
title: Dedicated Instance Terms
custom_edit_url: null
---
## General
Last revised: June 3, 2022
### Background
Within the scope of the Framework Agreement, the Customer may choose to purchase a subscription that requires a dedicated instance of ZITADEL. These additional terms for dedicated instance ("**Dedicated Instance Terms**") apply in addition to the Framework Agreement.
### Service
CAOS operates and manages a **Dedicated Instance** of ZITADEL in a private infrastructure environment dedicated for the Customer and provides support services for the Customer according the Purchase Order, these terms, agreed [**Service Level Description**](service-level-description), and [**Support Service Descriptions**](support-services).
Each Dedicated Instance consists, except agreed otherwise in writing, of a multi-zonal high-availability configuration that guarantees loads up to the specified [rate limits](rate-limit-policy#what-rate-limits-do-apply).
### Operations
CAOS will install and manage the Dedicated Instance on infracstructure provided by preferred cloud providers. Costs for infrastructure or cloud providers are not included in the Subscription, if not agreed otherwise in writing.
You may choose to provide the required infrastructure yourself. You must comply with the requirements and prerequisites outlined in the purchase order.
You may not modify, maintain or attempt to modify the Dedicated Instance, except with prior instructions by CAOS.
CAOS will use the same backup strategy as for ZITADEL Cloud (public cloud) services, except otherwise agreed between you and CAOS in writing.
### Maintenance and Updates
We will access, modify, and maintain the Dedicated Instance at times solely determined by CAOS (**"Regular Maintenance"**).
Under certain subscription plans, the Customer may agree a custom frequency and times for changes and updates. CAOS will coordinate the cadence and the changes with the Customer. To guarantee the quality of service, maintenance will occur on regular basis, typically monthly or sooner for security or performance related patches (**"Emergency Maintenance"**), but no longer than on quarterly basis.
If you fail to permit CAOS to conduct Regular Maintenance for 3 consecutive months or Emergency Maintenance within 5 days of notification, then CAOS will raise this issue with the Customer via Escalation Process. In case the issue is not resolved 5 days after such an escalation, CAOS may terminate the subscription with 30 days prior written notice to Customer. CAOS is not obligated to provide the service according to the terms and SLA, nor is CAOS liable to any security breach or damages after failure to permit Regular Maintenance for 3 consecutive months, or Emergency Maintenance for 5 days after notification.
### Incidents
Incidents are handled as documented in the [**Support Service Descriptions**](support-services). If the Customer choose in Purchase Order to provide the required infrastructure, then any incidents related to the infrastructure of the Dedicated Instance have to be resolved through the Customer directly.

View File

@@ -25,10 +25,6 @@ The following policies complement the TOS. When accepting the TOS, you accept th
* [**Acceptable Use Policy**](acceptable-use-policy) - What we understand as acceptable and fair use of our Services * [**Acceptable Use Policy**](acceptable-use-policy) - What we understand as acceptable and fair use of our Services
* [**Rate Limit Policy**](rate-limit-policy) - How we avoid overloads of our services * [**Rate Limit Policy**](rate-limit-policy) - How we avoid overloads of our services
This Agreement is extended with additional terms, in case your Subscription requires a Dedicated Instance. When you enter the Agreement with us, you accept these additional agreements.
* [**Dedicated Instance Terms**](terms-of-service-dedicated) - How we provide our services for a dedicated instance
### Alterations ### Alterations
Any provisions which deviate from these TOS must be agreed in writing between the Customer and us. Such agreements shall take precedence over the TOS outlined in this document. Any provisions which deviate from these TOS must be agreed in writing between the Customer and us. Such agreements shall take precedence over the TOS outlined in this document.
@@ -195,7 +191,7 @@ Should any provision of these TOS be or become invalid, this shall not affect th
These TOS shall enter into force as of 15.07.2022. These TOS shall enter into force as of 15.07.2022.
Last revised: June 14, 2022 Last revised: May 12, 2023
### Amendments ### Amendments

View File

@@ -83,7 +83,7 @@ module.exports = {
}, },
{ {
type: "doc", type: "doc",
docId: "legal/introduction", docId: "legal",
label: "Legal", label: "Legal",
position: "right", position: "right",
}, },

View File

@@ -15,7 +15,7 @@ http {
} }
location /docs { location /docs {
root /usr/share/nginx/html; alias /usr/share/nginx/html;
index /docs/index.html; index /docs/index.html;
try_files $uri $uri/ /docs/index.html?q=$query_string; try_files $uri $uri/ /docs/index.html?q=$query_string;
} }

View File

@@ -540,7 +540,18 @@ module.exports = {
support: [ support: [
], ],
legal: [ legal: [
"legal/introduction", {
type: "category",
label: "Legal Agreements",
collapsed: false,
link: {
type: "generated-index",
title: "Legal Agreements",
slug: "legal",
description:
"This section contains important agreements, policies and appendices relevant for users of our websites and services. All documents will be provided in English language.",
},
items: [
"legal/terms-of-service", "legal/terms-of-service",
"legal/data-processing-agreement", "legal/data-processing-agreement",
{ {
@@ -555,11 +566,10 @@ module.exports = {
}, },
{ {
type: "category", type: "category",
label: "Additional terms", label: "Support Program",
collapsed: true, collapsed: true,
items: [ items: [
"legal/terms-support-service", "legal/terms-support-service",
"legal/terms-of-service-dedicated",
], ],
}, },
{ {
@@ -573,5 +583,7 @@ module.exports = {
"legal/vulnerability-disclosure-policy", "legal/vulnerability-disclosure-policy",
], ],
}, },
]
},
], ],
}; };

View File

@@ -19,7 +19,6 @@ import (
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/api/ui/login" "github.com/zitadel/zitadel/internal/api/ui/login"
"github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/logstore"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/metrics" "github.com/zitadel/zitadel/internal/telemetry/metrics"
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
@@ -34,8 +33,7 @@ type API struct {
http1HostName string http1HostName string
grpcGateway *server.Gateway grpcGateway *server.Gateway
healthServer *health.Server healthServer *health.Server
cookieHandler *http_util.CookieHandler accessInterceptor *http_mw.AccessInterceptor
cookieConfig *http_mw.AccessConfig
queries *query.Queries queries *query.Queries
} }
@@ -51,9 +49,7 @@ func New(
verifier *internal_authz.TokenVerifier, verifier *internal_authz.TokenVerifier,
authZ internal_authz.Config, authZ internal_authz.Config,
tlsConfig *tls.Config, http2HostName, http1HostName string, tlsConfig *tls.Config, http2HostName, http1HostName string,
accessSvc *logstore.Service, accessInterceptor *http_mw.AccessInterceptor,
cookieHandler *http_util.CookieHandler,
cookieConfig *http_mw.AccessConfig,
) (_ *API, err error) { ) (_ *API, err error) {
api := &API{ api := &API{
port: port, port: port,
@@ -61,13 +57,12 @@ func New(
health: queries, health: queries,
router: router, router: router,
http1HostName: http1HostName, http1HostName: http1HostName,
cookieConfig: cookieConfig,
cookieHandler: cookieHandler,
queries: queries, queries: queries,
accessInterceptor: accessInterceptor,
} }
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessSvc) api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessInterceptor.AccessService())
api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, cookieHandler, cookieConfig) api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, accessInterceptor)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -90,8 +85,7 @@ func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayP
grpcServer, grpcServer,
a.port, a.port,
a.http1HostName, a.http1HostName,
a.cookieHandler, a.accessInterceptor,
a.cookieConfig,
a.queries, a.queries,
) )
if err != nil { if err != nil {

View File

@@ -16,7 +16,6 @@ import (
client_middleware "github.com/zitadel/zitadel/internal/api/grpc/client/middleware" client_middleware "github.com/zitadel/zitadel/internal/api/grpc/client/middleware"
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware" "github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
http_utils "github.com/zitadel/zitadel/internal/api/http"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
) )
@@ -69,13 +68,12 @@ type Gateway struct {
mux *runtime.ServeMux mux *runtime.ServeMux
http1HostName string http1HostName string
connection *grpc.ClientConn connection *grpc.ClientConn
cookieHandler *http_utils.CookieHandler accessInterceptor *http_mw.AccessInterceptor
cookieConfig *http_mw.AccessConfig
queries *query.Queries queries *query.Queries
} }
func (g *Gateway) Handler() http.Handler { func (g *Gateway) Handler() http.Handler {
return addInterceptors(g.mux, g.http1HostName, g.cookieHandler, g.cookieConfig, g.queries) return addInterceptors(g.mux, g.http1HostName, g.accessInterceptor, g.queries)
} }
type CustomHTTPResponse interface { type CustomHTTPResponse interface {
@@ -89,8 +87,7 @@ func CreateGatewayWithPrefix(
g WithGatewayPrefix, g WithGatewayPrefix,
port uint16, port uint16,
http1HostName string, http1HostName string,
cookieHandler *http_utils.CookieHandler, accessInterceptor *http_mw.AccessInterceptor,
cookieConfig *http_mw.AccessConfig,
queries *query.Queries, queries *query.Queries,
) (http.Handler, string, error) { ) (http.Handler, string, error) {
runtimeMux := runtime.NewServeMux(serveMuxOptions...) runtimeMux := runtime.NewServeMux(serveMuxOptions...)
@@ -106,10 +103,10 @@ func CreateGatewayWithPrefix(
if err != nil { if err != nil {
return nil, "", fmt.Errorf("failed to register grpc gateway: %w", err) return nil, "", fmt.Errorf("failed to register grpc gateway: %w", err)
} }
return addInterceptors(runtimeMux, http1HostName, cookieHandler, cookieConfig, queries), g.GatewayPathPrefix(), nil return addInterceptors(runtimeMux, http1HostName, accessInterceptor, queries), g.GatewayPathPrefix(), nil
} }
func CreateGateway(ctx context.Context, port uint16, http1HostName string, cookieHandler *http_utils.CookieHandler, cookieConfig *http_mw.AccessConfig) (*Gateway, error) { func CreateGateway(ctx context.Context, port uint16, http1HostName string, accessInterceptor *http_mw.AccessInterceptor) (*Gateway, error) {
connection, err := dial(ctx, connection, err := dial(ctx,
port, port,
[]grpc.DialOption{ []grpc.DialOption{
@@ -124,8 +121,7 @@ func CreateGateway(ctx context.Context, port uint16, http1HostName string, cooki
mux: runtimeMux, mux: runtimeMux,
http1HostName: http1HostName, http1HostName: http1HostName,
connection: connection, connection: connection,
cookieHandler: cookieHandler, accessInterceptor: accessInterceptor,
cookieConfig: cookieConfig,
}, nil }, nil
} }
@@ -163,8 +159,7 @@ func dial(ctx context.Context, port uint16, opts []grpc.DialOption) (*grpc.Clien
func addInterceptors( func addInterceptors(
handler http.Handler, handler http.Handler,
http1HostName string, http1HostName string,
cookieHandler *http_utils.CookieHandler, accessInterceptor *http_mw.AccessInterceptor,
cookieConfig *http_mw.AccessConfig,
queries *query.Queries, queries *query.Queries,
) http.Handler { ) http.Handler {
handler = http_mw.CallDurationHandler(handler) handler = http_mw.CallDurationHandler(handler)
@@ -174,7 +169,7 @@ func addInterceptors(
handler = http_mw.DefaultTelemetryHandler(handler) handler = http_mw.DefaultTelemetryHandler(handler)
// For some non-obvious reason, the exhaustedCookieInterceptor sends the SetCookie header // For some non-obvious reason, the exhaustedCookieInterceptor sends the SetCookie header
// only if it follows the http_mw.DefaultTelemetryHandler // only if it follows the http_mw.DefaultTelemetryHandler
handler = exhaustedCookieInterceptor(handler, cookieHandler, cookieConfig, queries) handler = exhaustedCookieInterceptor(handler, accessInterceptor, queries)
handler = http_mw.DefaultMetricsHandler(handler) handler = http_mw.DefaultMetricsHandler(handler)
return handler return handler
} }
@@ -193,15 +188,13 @@ func http1Host(next http.Handler, http1HostName string) http.Handler {
func exhaustedCookieInterceptor( func exhaustedCookieInterceptor(
next http.Handler, next http.Handler,
cookieHandler *http_utils.CookieHandler, accessInterceptor *http_mw.AccessInterceptor,
cookieConfig *http_mw.AccessConfig,
queries *query.Queries, queries *query.Queries,
) http.Handler { ) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
next.ServeHTTP(&cookieResponseWriter{ next.ServeHTTP(&cookieResponseWriter{
ResponseWriter: writer, ResponseWriter: writer,
cookieHandler: cookieHandler, accessInterceptor: accessInterceptor,
cookieConfig: cookieConfig,
request: request, request: request,
queries: queries, queries: queries,
}, request) }, request)
@@ -210,18 +203,17 @@ func exhaustedCookieInterceptor(
type cookieResponseWriter struct { type cookieResponseWriter struct {
http.ResponseWriter http.ResponseWriter
cookieHandler *http_utils.CookieHandler accessInterceptor *http_mw.AccessInterceptor
cookieConfig *http_mw.AccessConfig
request *http.Request request *http.Request
queries *query.Queries queries *query.Queries
} }
func (r *cookieResponseWriter) WriteHeader(status int) { func (r *cookieResponseWriter) WriteHeader(status int) {
if status >= 200 && status < 300 { if status >= 200 && status < 300 {
http_mw.DeleteExhaustedCookie(r.cookieHandler, r.ResponseWriter, r.request, r.cookieConfig) r.accessInterceptor.DeleteExhaustedCookie(r.ResponseWriter, r.request)
} }
if status == http.StatusTooManyRequests { if status == http.StatusTooManyRequests {
http_mw.SetExhaustedCookie(r.cookieHandler, r.ResponseWriter, r.cookieConfig, r.request) r.accessInterceptor.SetExhaustedCookie(r.ResponseWriter, r.request)
} }
r.ResponseWriter.WriteHeader(status) r.ResponseWriter.WriteHeader(status)
} }

View File

@@ -1,6 +1,7 @@
package middleware package middleware
import ( import (
"context"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@@ -32,15 +33,54 @@ type AccessConfig struct {
// NewAccessInterceptor intercepts all requests and stores them to the logstore. // NewAccessInterceptor intercepts all requests and stores them to the logstore.
// If storeOnly is false, it also checks if requests are exhausted. // If storeOnly is false, it also checks if requests are exhausted.
// If requests are exhausted, it also returns http.StatusTooManyRequests and sets a cookie // If requests are exhausted, it also returns http.StatusTooManyRequests and sets a cookie
func NewAccessInterceptor(svc *logstore.Service, cookieHandler *http_utils.CookieHandler, cookieConfig *AccessConfig, storeOnly bool) *AccessInterceptor { func NewAccessInterceptor(svc *logstore.Service, cookieHandler *http_utils.CookieHandler, cookieConfig *AccessConfig) *AccessInterceptor {
return &AccessInterceptor{ return &AccessInterceptor{
svc: svc, svc: svc,
cookieHandler: cookieHandler, cookieHandler: cookieHandler,
limitConfig: cookieConfig, limitConfig: cookieConfig,
storeOnly: storeOnly,
} }
} }
func (a *AccessInterceptor) WithoutLimiting() *AccessInterceptor {
return &AccessInterceptor{
svc: a.svc,
cookieHandler: a.cookieHandler,
limitConfig: a.limitConfig,
storeOnly: true,
}
}
func (a *AccessInterceptor) AccessService() *logstore.Service {
return a.svc
}
func (a *AccessInterceptor) Limit(ctx context.Context) bool {
if !a.svc.Enabled() || a.storeOnly {
return false
}
instance := authz.GetInstance(ctx)
remaining := a.svc.Limit(ctx, instance.InstanceID())
return remaining != nil && *remaining <= 0
}
func (a *AccessInterceptor) SetExhaustedCookie(writer http.ResponseWriter, request *http.Request) {
cookieValue := "true"
host := request.Header.Get(middleware.HTTP1Host)
domain := host
if strings.ContainsAny(host, ":") {
var err error
domain, _, err = net.SplitHostPort(host)
if err != nil {
logging.WithError(err).WithField("host", host).Warning("failed to extract cookie domain from request host")
}
}
a.cookieHandler.SetCookie(writer, a.limitConfig.ExhaustedCookieKey, domain, cookieValue)
}
func (a *AccessInterceptor) DeleteExhaustedCookie(writer http.ResponseWriter, request *http.Request) {
a.cookieHandler.DeleteCookie(writer, request, a.limitConfig.ExhaustedCookieKey)
}
func (a *AccessInterceptor) Handle(next http.Handler) http.Handler { func (a *AccessInterceptor) Handle(next http.Handler) http.Handler {
if !a.svc.Enabled() { if !a.svc.Enabled() {
return next return next
@@ -49,23 +89,16 @@ func (a *AccessInterceptor) Handle(next http.Handler) http.Handler {
ctx := request.Context() ctx := request.Context()
tracingCtx, checkSpan := tracing.NewNamedSpan(ctx, "checkAccess") tracingCtx, checkSpan := tracing.NewNamedSpan(ctx, "checkAccess")
wrappedWriter := &statusRecorder{ResponseWriter: writer, status: 0} wrappedWriter := &statusRecorder{ResponseWriter: writer, status: 0}
instance := authz.GetInstance(ctx) limited := a.Limit(tracingCtx)
limit := false
if !a.storeOnly {
remaining := a.svc.Limit(tracingCtx, instance.InstanceID())
limit = remaining != nil && *remaining == 0
}
checkSpan.End() checkSpan.End()
if limit { if limited {
// Limit can only be true when storeOnly is false, so set the cookie and the response code a.SetExhaustedCookie(wrappedWriter, request)
SetExhaustedCookie(a.cookieHandler, wrappedWriter, a.limitConfig, request)
http.Error(wrappedWriter, "quota for authenticated requests is exhausted", http.StatusTooManyRequests) http.Error(wrappedWriter, "quota for authenticated requests is exhausted", http.StatusTooManyRequests)
} else {
if !a.storeOnly {
// If not limited and not storeOnly, ensure the cookie is deleted
DeleteExhaustedCookie(a.cookieHandler, wrappedWriter, request, a.limitConfig)
} }
// Always serve if not limited if !limited && !a.storeOnly {
a.DeleteExhaustedCookie(wrappedWriter, request)
}
if !limited {
next.ServeHTTP(wrappedWriter, request) next.ServeHTTP(wrappedWriter, request)
} }
tracingCtx, writeSpan := tracing.NewNamedSpan(tracingCtx, "writeAccess") tracingCtx, writeSpan := tracing.NewNamedSpan(tracingCtx, "writeAccess")
@@ -75,6 +108,7 @@ func (a *AccessInterceptor) Handle(next http.Handler) http.Handler {
if err != nil { if err != nil {
logging.WithError(err).WithField("url", requestURL).Warning("failed to unescape request url") logging.WithError(err).WithField("url", requestURL).Warning("failed to unescape request url")
} }
instance := authz.GetInstance(tracingCtx)
a.svc.Handle(tracingCtx, &access.Record{ a.svc.Handle(tracingCtx, &access.Record{
LogDate: time.Now(), LogDate: time.Now(),
Protocol: access.HTTP, Protocol: access.HTTP,
@@ -90,24 +124,6 @@ func (a *AccessInterceptor) Handle(next http.Handler) http.Handler {
}) })
} }
func SetExhaustedCookie(cookieHandler *http_utils.CookieHandler, writer http.ResponseWriter, cookieConfig *AccessConfig, request *http.Request) {
cookieValue := "true"
host := request.Header.Get(middleware.HTTP1Host)
domain := host
if strings.ContainsAny(host, ":") {
var err error
domain, _, err = net.SplitHostPort(host)
if err != nil {
logging.WithError(err).WithField("host", host).Warning("failed to extract cookie domain from request host")
}
}
cookieHandler.SetCookie(writer, cookieConfig.ExhaustedCookieKey, domain, cookieValue)
}
func DeleteExhaustedCookie(cookieHandler *http_utils.CookieHandler, writer http.ResponseWriter, request *http.Request, cookieConfig *AccessConfig) {
cookieHandler.DeleteCookie(writer, request, cookieConfig.ExhaustedCookieKey)
}
type statusRecorder struct { type statusRecorder struct {
http.ResponseWriter http.ResponseWriter
status int status int

View File

@@ -91,7 +91,7 @@ func (f *file) Stat() (_ fs.FileInfo, err error) {
return f, nil return f, nil
} }
func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, callDurationInterceptor, instanceHandler, accessInterceptor func(http.Handler) http.Handler, customerPortal string) (http.Handler, error) { func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, callDurationInterceptor, instanceHandler func(http.Handler) http.Handler, limitingAccessInterceptor *middleware.AccessInterceptor, customerPortal string) (http.Handler, error) {
fSys, err := fs.Sub(static, "static") fSys, err := fs.Sub(static, "static")
if err != nil { if err != nil {
return nil, err return nil, err
@@ -106,10 +106,11 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, call
handler := mux.NewRouter() handler := mux.NewRouter()
handler.Use(callDurationInterceptor, instanceHandler, security, accessInterceptor) handler.Use(callDurationInterceptor, instanceHandler, security, limitingAccessInterceptor.WithoutLimiting().Handle)
handler.Handle(envRequestPath, middleware.TelemetryHandler()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler.Handle(envRequestPath, middleware.TelemetryHandler()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
url := http_util.BuildOrigin(r.Host, externalSecure) url := http_util.BuildOrigin(r.Host, externalSecure)
instance := authz.GetInstance(r.Context()) ctx := r.Context()
instance := authz.GetInstance(ctx)
instanceMgmtURL, err := templateInstanceManagementURL(config.InstanceManagementURL, instance) instanceMgmtURL, err := templateInstanceManagementURL(config.InstanceManagementURL, instance)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("unable to template instance management url for console: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("unable to template instance management url for console: %v", err), http.StatusInternalServerError)
@@ -120,6 +121,11 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, call
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
return return
} }
if limitingAccessInterceptor.Limit(ctx) {
limitingAccessInterceptor.SetExhaustedCookie(w, r)
} else {
limitingAccessInterceptor.DeleteExhaustedCookie(w, r)
}
_, err = w.Write(environmentJSON) _, err = w.Write(environmentJSON)
logging.OnError(err).Error("error serving environment.json") logging.OnError(err).Error("error serving environment.json")
}))) })))

View File

@@ -327,7 +327,12 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) {
var msg string var msg string
if err != nil { if err != nil {
logging.WithError(err).WithField("auth_req_id", authReq.ID).Error() log := logging.WithError(err)
if authReq != nil {
log = log.WithField("auth_req_id", authReq.ID)
}
log.Error()
_, msg = l.getErrorMessage(r, err) _, msg = l.getErrorMessage(r, err)
} }
data := l.getBaseData(r, authReq, "Errors.Internal", "", "Internal", msg) data := l.getBaseData(r, authReq, "Errors.Internal", "", "Internal", msg)

View File

@@ -229,7 +229,7 @@ service SystemService {
}; };
} }
// Returns the domain of an instance // Removes the domain of an instance
rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) { rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) {
option (google.api.http) = { option (google.api.http) = {
delete: "/instances/{instance_id}/domains/{domain}"; delete: "/instances/{instance_id}/domains/{domain}";
@@ -240,7 +240,7 @@ service SystemService {
}; };
} }
// Returns the domain of an instance // Sets the primary domain of an instance
rpc SetPrimaryDomain(SetPrimaryDomainRequest) returns (SetPrimaryDomainResponse) { rpc SetPrimaryDomain(SetPrimaryDomainRequest) returns (SetPrimaryDomainResponse) {
option (google.api.http) = { option (google.api.http) = {
post: "/instances/{instance_id}/domains/_set_primary"; post: "/instances/{instance_id}/domains/_set_primary";