mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 19:09:16 +00:00
Merge branch 'main' into eventstore-created-at
This commit is contained in:
@@ -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
|
||||||
|
2
.github/workflows/zitadel.yml
vendored
2
.github/workflows/zitadel.yml
vendored
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
12
docs/.gitignore
vendored
@@ -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
|
||||||
|
@@ -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>
|
|
@@ -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.
|
|
@@ -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
|
||||||
|
|
||||||
|
@@ -83,7 +83,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "doc",
|
type: "doc",
|
||||||
docId: "legal/introduction",
|
docId: "legal",
|
||||||
label: "Legal",
|
label: "Legal",
|
||||||
position: "right",
|
position: "right",
|
||||||
},
|
},
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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")
|
||||||
})))
|
})))
|
||||||
|
@@ -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)
|
||||||
|
@@ -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";
|
||||||
|
Reference in New Issue
Block a user