From e22c00bdf72781d170f08f9f22e0d8754bcce3d4 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Mon, 21 Aug 2023 10:26:55 +0100 Subject: [PATCH 1/3] Add missing copyright headers Signed-off-by: Sergei Trofimov --- capability/well-known.go | 2 ++ deployments/docker/src/builder-dispatcher | 2 ++ deployments/docker/src/service-entrypoint | 2 ++ integration-tests/utils/checkers.py | 2 ++ integration-tests/utils/conftest.py | 2 ++ integration-tests/utils/generators.py | 2 ++ integration-tests/utils/hooks.py | 2 ++ integration-tests/utils/util.py | 2 ++ perf/do-runs | 2 ++ perf/scripts/extract-gin-logs | 2 ++ perf/scripts/run-loads.py | 2 ++ scripts/extract-corim | 2 ++ scripts/extract-evidence-claims | 2 ++ 13 files changed, 26 insertions(+) diff --git a/capability/well-known.go b/capability/well-known.go index 413b5791..9f818fcd 100644 --- a/capability/well-known.go +++ b/capability/well-known.go @@ -1,3 +1,5 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package capability import ( diff --git a/deployments/docker/src/builder-dispatcher b/deployments/docker/src/builder-dispatcher index c9da57e9..74175878 100755 --- a/deployments/docker/src/builder-dispatcher +++ b/deployments/docker/src/builder-dispatcher @@ -1,4 +1,6 @@ #!/bin/bash +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 # This script is the entry point for the builder docker container. _ERROR='\e[0;31mERROR\e[0m' diff --git a/deployments/docker/src/service-entrypoint b/deployments/docker/src/service-entrypoint index a57264aa..16d89da7 100755 --- a/deployments/docker/src/service-entrypoint +++ b/deployments/docker/src/service-entrypoint @@ -1,4 +1,6 @@ #!/bin/bash +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 umask 0002 exec "$@" diff --git a/integration-tests/utils/checkers.py b/integration-tests/utils/checkers.py index 8a825073..f328e570 100644 --- a/integration-tests/utils/checkers.py +++ b/integration-tests/utils/checkers.py @@ -1,3 +1,5 @@ +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 import os import json from datetime import datetime, timedelta diff --git a/integration-tests/utils/conftest.py b/integration-tests/utils/conftest.py index e0643f6e..94959604 100644 --- a/integration-tests/utils/conftest.py +++ b/integration-tests/utils/conftest.py @@ -1,3 +1,5 @@ +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 import os import sys diff --git a/integration-tests/utils/generators.py b/integration-tests/utils/generators.py index e1fcd293..83c1e7ed 100644 --- a/integration-tests/utils/generators.py +++ b/integration-tests/utils/generators.py @@ -1,3 +1,5 @@ +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 import ast import os import shutil diff --git a/integration-tests/utils/hooks.py b/integration-tests/utils/hooks.py index a5fe8b85..a74a48b0 100644 --- a/integration-tests/utils/hooks.py +++ b/integration-tests/utils/hooks.py @@ -1,3 +1,5 @@ +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 import os from generators import * diff --git a/integration-tests/utils/util.py b/integration-tests/utils/util.py index 34694db3..7a8f46a4 100644 --- a/integration-tests/utils/util.py +++ b/integration-tests/utils/util.py @@ -1,3 +1,5 @@ +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 import json import re import subprocess diff --git a/perf/do-runs b/perf/do-runs index ccaa3751..0f1b8a55 100755 --- a/perf/do-runs +++ b/perf/do-runs @@ -1,4 +1,6 @@ #!/bin/bash +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 THIS_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) VERAISON=$THIS_DIR/../deployments/docker/veraison diff --git a/perf/scripts/extract-gin-logs b/perf/scripts/extract-gin-logs index 39602577..33acde25 100755 --- a/perf/scripts/extract-gin-logs +++ b/perf/scripts/extract-gin-logs @@ -1,4 +1,6 @@ #!/usr/bin/env python +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 import csv import logging from datetime import datetime, timedelta diff --git a/perf/scripts/run-loads.py b/perf/scripts/run-loads.py index 7d12a8b5..9dd60a81 100755 --- a/perf/scripts/run-loads.py +++ b/perf/scripts/run-loads.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 import argparse import multiprocessing import os diff --git a/scripts/extract-corim b/scripts/extract-corim index ecaae67c..e301ed6d 100755 --- a/scripts/extract-corim +++ b/scripts/extract-corim @@ -1,4 +1,6 @@ #!/bin/bash +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 # This script is used to extract the claims structure (serialized CBOR) from COSE tokens. # On Arch, you need in stall yq and ruby-cbor-diag (AUR) packages. # On Ubuntu, install cbor-diag gem; yq seems to only be available via *shudder* snap... diff --git a/scripts/extract-evidence-claims b/scripts/extract-evidence-claims index e23c5984..568dbeee 100755 --- a/scripts/extract-evidence-claims +++ b/scripts/extract-evidence-claims @@ -1,4 +1,6 @@ #!/bin/bash +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 # This script is used to extract the claims structure (serialized CBOR) from COSE tokens. # On Arch, you need in stall yq and ruby-cbor-diag (AUR) packages. # On Ubuntu, install cbor-diag gem; yq seems to only be available via *shudder* snap... From ea5b494d7c1311b174416b9296b7bb626a897b46 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Mon, 21 Aug 2023 15:10:46 +0100 Subject: [PATCH 2/3] Implement API auth Implement API authentication and authorization. - Define IAuthorizer interface that can be used to obtain a gin middleware handler that performs authorisation. - Add a mechanism to obtain an IAuthorizer for a particular role base on Veraison configuration. - Implement "passthrough" authorizer that duplicates existing behavior (no auth). - Implement "basic" authorizer that does not rely on an external authorization server. - Implement "keycloak" authorizer that uses Keycloak service for authentication. - Update provisioning service to authorize based on "provisioner" role. - Update management service to authorize based on "manager" role. - Add the previously missing README for the management service. Signed-off-by: Sergei Trofimov --- auth/README.md | 133 ++ auth/authorizer.go | 47 + auth/basic.go | 153 ++ auth/iauthorizer.go | 25 + auth/keycloak.go | 108 + auth/passthrough.go | 37 + auth/problem.go | 22 + auth/roles.go | 7 + deployments/docker/Makefile | 19 +- deployments/docker/deployment.cfg | 1 + deployments/docker/src/builder-dispatcher | 2 + deployments/docker/src/config.yaml.template | 4 + deployments/docker/src/keycloak.conf.template | 5 + deployments/docker/src/keycloak.docker | 17 + deployments/docker/src/load-config.mk | 1 + deployments/docker/src/veraison-realm.json | 1841 +++++++++++++++++ deployments/docker/veraison | 29 +- go.mod | 12 +- go.sum | 40 +- integration-tests/docker/bashrc | 1 + integration-tests/tests/common.yaml | 11 + .../tests/test_enacttrust_badkey.tavern.yaml | 1 + .../tests/test_enacttrust_badnode.tavern.yaml | 1 + .../tests/test_end_to_end.tavern.yaml | 1 + .../tests/test_multinonce.tavern.yaml | 1 + .../tests/test_policy_management.tavern.yaml | 14 + .../test_provisioning_empty_body.tavern.yaml | 1 + ...test_provisioning_unauthorized.tavern.yaml | 23 + ...ification_bad_session_attester.tavern.yaml | 1 + integration-tests/utils/conftest.py | 1 + integration-tests/utils/hooks.py | 24 +- integration-tests/utils/util.py | 37 + management/api/router.go | 8 +- management/cmd/management-service/README.md | 39 + management/cmd/management-service/config.yaml | 5 + management/cmd/management-service/main.go | 18 +- provisioning/api/handler_test.go | 13 +- provisioning/api/router.go | 4 +- .../cmd/provisioning-service/README.md | 7 +- provisioning/cmd/provisioning-service/main.go | 20 +- 40 files changed, 2706 insertions(+), 28 deletions(-) create mode 100644 auth/README.md create mode 100644 auth/authorizer.go create mode 100644 auth/basic.go create mode 100644 auth/iauthorizer.go create mode 100644 auth/keycloak.go create mode 100644 auth/passthrough.go create mode 100644 auth/problem.go create mode 100644 auth/roles.go create mode 100644 deployments/docker/src/keycloak.conf.template create mode 100644 deployments/docker/src/keycloak.docker create mode 100644 deployments/docker/src/veraison-realm.json create mode 100644 integration-tests/tests/test_provisioning_unauthorized.tavern.yaml create mode 100644 management/cmd/management-service/README.md diff --git a/auth/README.md b/auth/README.md new file mode 100644 index 00000000..f47d87b6 --- /dev/null +++ b/auth/README.md @@ -0,0 +1,133 @@ +## Overview + +This directory implements authentication and authorization for Veraison API. +Authentication can be performed using the Basic HTTP scheme (with the `basic` +backend), or using a Bearer token (with the `keycloak` backend). Once an API +user is authenticated, authorization is +[role-based](https://en.wikipedia.org/wiki/Role-based_access_control). See +documentation for specific services for which role(s) are needed to access +their API. + + +## Configuration + +- `backend`: specifies which auth backend will be used by the service. The + valid options are: + + - `passthrough`: a backend that does not perform any authentication, allowing + all requests. + - `none`: alias for `passthrough`. + - `basic`: Uses the Basic HTTP authentication scheme. See + [RFC7617](https://datatracker.ietf.org/doc/html/rfc7617) for details. This + is not intended for production. + - `keycloak`: Uses OpenID Connect protocol as implemented by the Keycloak + authentication server. + + See below for details of how to configure individual backends. + +### Passthrough + +No additional configuration is required. `passthrough` will allow all requests. +This is the default if `auth` configuration is not provided. + +### Basic + +- `users`: this is a mapping of user names onto their bcrypt password hashes + and roles. The key of the mapping is the user name, the value is a further + mapping for the details with the following fields: + + - `password`: the bcrypt hash of the user's password. + - `roles`: either a single role or a list of roles associated with the + user. API authrization will be performed based on the user's roles. + +On Linux, bcrypt hashes can be generated on the command line using `mkpasswd` +utility, e.g.: + +```bash +mkpasswd -m bcrypt --stdin <<< Passw0rd! +``` + +For example: + +```yaml +auth: + backend: basic + users: + user1: + password: "$2b$05$XgVBveh6QPrRHXI.8S/J9uobBR7Wv9z4CL8yACHEmKIQmYSSyKAqC" # Passw0rd! + roles: provisioner + user2: + password: "$2b$05$x5fvAV5WPkX0KXzqf5FMKODz0uyi2ioew1lOrF2Czp2aNH1LQmhki" # @s3cr3t + roles: [manager, provisioner] +``` + +### Keycloak + +- `host` (optional): host name of the Keycloak service. Defaults to + `localhost`. +- `port` (optional): the port on which the Keycloak service is listening. + Defaults to `8080`. +- `realm` (optional): the Keycloak realm used by Veraison. A realm contains the + configuration for clients, users, roles, etc. It is roughly analogous to a + "tenant id". Defaults to `veraison`. + +For example: + +```yaml +auth: + backend: keycloak + host: keycloak.example.com + port: 11111 + realm: veraison +``` + +## Usage + +```go +"github.com/gin-gonic/gin" +"github.com/veraison/services/auth" +"github.com/veraison/services/config" +"github.com/veraison/services/log" + +func main() { + // Load authroizer config. + v, err := config.ReadRawConfig(*config.File, false) + if err != nil { + log.Fatalf("Could not read config: %v", err) + } + subs, err := config.GetSubs(v, "*auth") + if err != nil { + log.Fatalf("Could not parse config: %v", err) + } + + // Create new authorizer based on the loaded config. + authorizer, err := auth.NewAuthorizer(subs["auth"], log.Named("auth")) + if err != nil { + log.Fatalf("could not init authorizer: %v", err) + } + + // Ensure the authorizer is terminated properly on exit + defer func() { + err := authorizer.Close() + if err != nil { + log.Errorf("Could not close authorizer: %v", err) + } + }() + + // Use the authorizer to set a middleware handler in the appropriate gin + // router, with an appropriate role. + router := gin.Default() + router.Use(authorizer.GetGinHandler(auth.ManagerRole)) + + // Set up route handling here + // ... + // ... + + // Run the service. + if err := router.Run("0.0.0.0:80"); err != nil { + log.Errorf("Gin engine failed: %v", err) + } +} + +``` + diff --git a/auth/authorizer.go b/auth/authorizer.go new file mode 100644 index 00000000..26a2be45 --- /dev/null +++ b/auth/authorizer.go @@ -0,0 +1,47 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package auth + +import ( + "fmt" + + "github.com/spf13/viper" + "github.com/veraison/services/config" + "go.uber.org/zap" +) + +type cfg struct { + Backend string `mapstructure:"backend,omitempty"` + BackendConfigs map[string]interface{} `mapstructure:",remain"` +} + +func NewAuthorizer(v *viper.Viper, logger *zap.SugaredLogger) (IAuthorizer, error) { + cfg := cfg{ + Backend: "passthrough", + } + + loader := config.NewLoader(&cfg) + if err := loader.LoadFromViper(v); err != nil { + return nil, err + } + + var a IAuthorizer + + switch cfg.Backend { + case "none", "passthrough": + a = &PassthroughAuthorizer{} + case "basic": + a = &BasicAuthorizer{} + case "keycloak": + a = &KeycloakAuthorizer{} + default: + return nil, fmt.Errorf("backend %q is not supported", cfg.Backend) + } + + if err := a.Init(v, logger); err != nil { + return nil, err + } + + return a, nil +} diff --git a/auth/basic.go b/auth/basic.go new file mode 100644 index 00000000..fe86c298 --- /dev/null +++ b/auth/basic.go @@ -0,0 +1,153 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package auth + +import ( + "errors" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/spf13/viper" + "github.com/veraison/services/log" + "go.uber.org/zap" + "golang.org/x/crypto/bcrypt" +) + +type basicAuthUser struct { + Password string `mapstructure:"password"` + Roles []string `mapstructure:"roles"` +} + +func newBasicAuthUser(m map[string]interface{}) (*basicAuthUser, error) { + var newUser basicAuthUser + + passRaw, ok := m["password"] + if !ok { + return nil, errors.New("password not set") + } + + switch t := passRaw.(type) { + case string: + newUser.Password = t + default: + return nil, fmt.Errorf("invalid password: expected string found %T", t) + } + + rolesRaw, ok := m["roles"] + if ok { + switch t := rolesRaw.(type) { + case []string: + newUser.Roles = t + case string: + newUser.Roles = make([]string, 1) + newUser.Roles[0] = t + default: + return nil, fmt.Errorf( + "invalid roles: expected []string or string, found %T", + t, + ) + } + } else { + newUser.Roles = make([]string, 0) + } + + return &newUser, nil +} + +type BasicAuthorizer struct { + logger *zap.SugaredLogger + users map[string]*basicAuthUser +} + +func (o *BasicAuthorizer) Init(v *viper.Viper, logger *zap.SugaredLogger) error { + if logger == nil { + return errors.New("nil logger") + } + o.logger = logger + + o.users = make(map[string]*basicAuthUser) + if rawUsers := v.GetStringMap("users"); rawUsers != nil { + for name, rawUser := range rawUsers { + switch t := rawUser.(type) { + case map[string]interface{}: + newUser, err := newBasicAuthUser(t) + if err != nil { + return fmt.Errorf("invalid user %q: %w", name, err) + + } + o.logger.Debugw("registered user", + "user", name, + "password", newUser.Password, + "roles", newUser.Roles, + ) + o.users[name] = newUser + default: + return fmt.Errorf( + "invalid user %q: expected map[string]interface{}, got %T", + name, t, + ) + } + } + } + + return nil +} + +func (o *BasicAuthorizer) Close() error { + return nil +} + +func (o *BasicAuthorizer) GetGinHandler(role string) gin.HandlerFunc { + return func(c *gin.Context) { + o.logger.Debugw("auth basic", "path", c.Request.URL.Path) + + userName, password, hasAuth := c.Request.BasicAuth() + if !hasAuth { + c.Writer.Header().Set("WWW-Authenticate", "Basic realm=veraison") + ReportProblem(c, http.StatusUnauthorized, + "no Basic Authorizaiton given") + return + } + + userInfo, ok := o.users[userName] + if !ok { + c.Writer.Header().Set("WWW-Authenticate", "Basic realm=veraison") + ReportProblem(c, http.StatusUnauthorized, + "no Basic Authorizaiton given") + return + } + + if err := bcrypt.CompareHashAndPassword( + []byte(userInfo.Password), + []byte(password), + ); err != nil { + o.logger.Debugf("password check failed: %v", err) + c.Writer.Header().Set("WWW-Authenticate", "Basic realm=veraison") + ReportProblem(c, http.StatusUnauthorized, + "wrong username or password") + return + } + + gotRole := false + if role == NoRole { + gotRole = true + } else { + for _, userRole := range userInfo.Roles { + if userRole == role { + gotRole = true + break + } + } + } + + if gotRole { + log.Debugw("user authenticated", "user", userName, "role", role) + } else { + c.Writer.Header().Set("WWW-Authenticate", "Basic realm=veraison") + ReportProblem(c, http.StatusUnauthorized, + "API unauthorized for user") + } + } +} diff --git a/auth/iauthorizer.go b/auth/iauthorizer.go new file mode 100644 index 00000000..11762e06 --- /dev/null +++ b/auth/iauthorizer.go @@ -0,0 +1,25 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package auth + +import ( + "github.com/gin-gonic/gin" + "github.com/spf13/viper" + "go.uber.org/zap" +) + +// IAuthorizer defines the interface that must be implemented by the veraison +// auth backends. +type IAuthorizer interface { + // Init initializes the backend based on the configuration inside the + // provided Viper object and using the provided logger. + Init(v *viper.Viper, logger *zap.SugaredLogger) error + // Close terminates the backend. The exact nature of this method is + // backend-specific. + Close() error + // GetGinHandler returns a gin.HandlerFunc that performs authorization + // based on the specified role. This function can be set as gin + // middleware by passing it to gin.Engine.Use(). + GetGinHandler(role string) gin.HandlerFunc +} diff --git a/auth/keycloak.go b/auth/keycloak.go new file mode 100644 index 00000000..c9bcd4ef --- /dev/null +++ b/auth/keycloak.go @@ -0,0 +1,108 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package auth + +import ( + "errors" + "flag" + "fmt" + + "github.com/gin-gonic/gin" + "github.com/spf13/viper" + "github.com/tbaehler/gin-keycloak/pkg/ginkeycloak" + "github.com/veraison/services/config" + "go.uber.org/zap" + "gopkg.in/square/go-jose.v2/jwt" +) + +type keycloakCfg struct { + Backend string `mapstructure:"backend"` + Host string `mapstructure:"host"` + Port string `mapstructure:"port"` + Realm string `mapstructure:"realm"` +} + +type KeycloakAuthorizer struct { + logger *zap.SugaredLogger + config ginkeycloak.KeycloakConfig +} + +func (o *KeycloakAuthorizer) Init(v *viper.Viper, logger *zap.SugaredLogger) error { + if logger == nil { + return errors.New("nil logger") + } + o.logger = logger + + // This prevents glog--the logging package used by ginkeycloak--from complaining. + flag.Parse() + + cfg := keycloakCfg{ + Host: "localhost", + Port: "1111", + Realm: "veraison", + } + + loader := config.NewLoader(&cfg) + if err := loader.LoadFromViper(v); err != nil { + return err + } + + o.config = ginkeycloak.KeycloakConfig{ + Url: fmt.Sprintf("http://%s:%s", cfg.Host, cfg.Port), + Realm: cfg.Realm, + CustomClaimsMapper: mapTenantID, + } + return nil +} + +func (o *KeycloakAuthorizer) Close() error { + return nil +} + +func (o *KeycloakAuthorizer) GetGinHandler(role string) gin.HandlerFunc { + return ginkeycloak.Auth(o.getAuthCheck([]string{role}), o.config) +} + +func (o *KeycloakAuthorizer) getAuthCheck( + roles []string, +) ginkeycloak.AccessCheckFunction { + return func(tc *ginkeycloak.TokenContainer, ctx *gin.Context) bool { + ctx.Set("token", *tc.KeyCloakToken) + ctx.Set("uid", tc.KeyCloakToken.PreferredUsername) + + roleOK := ginkeycloak.RealmCheck(roles)(tc, ctx) + + o.logger.Debugw("auth check", "role", roleOK) + + return roleOK + } +} + +func mapTenantID(jsonWebToken *jwt.JSONWebToken, keyCloakToken *ginkeycloak.KeyCloakToken) error { + var claims map[string]interface{} + + // note: this mapper function will only be called once the JWT had + // alreadybeen verified by ginkeycloak, so extracting claims without + // verification here is, in fact, safe. + if err := jsonWebToken.UnsafeClaimsWithoutVerification(&claims); err != nil { + return err + } + + var tenantId string + rawTenantId, ok := claims["tenant_id"] + if ok { + tenantId, ok = rawTenantId.(string) + if !ok { + return fmt.Errorf("tenant_id not a string: %v (%T)", + rawTenantId, rawTenantId) + } + } else { + tenantId = "" + } + + keyCloakToken.CustomClaims = map[string]string{ + "tenant_id": tenantId, + } + + return nil +} diff --git a/auth/passthrough.go b/auth/passthrough.go new file mode 100644 index 00000000..9ab56de6 --- /dev/null +++ b/auth/passthrough.go @@ -0,0 +1,37 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package auth + +import ( + "errors" + + "github.com/gin-gonic/gin" + "github.com/spf13/viper" + "go.uber.org/zap" +) + +type PassthroughAuthorizer struct { + logger *zap.SugaredLogger +} + +func NewPassthroughAuthorizer(logger *zap.SugaredLogger) IAuthorizer { + return &PassthroughAuthorizer{logger: logger} +} + +func (o *PassthroughAuthorizer) Init(v *viper.Viper, logger *zap.SugaredLogger) error { + if logger == nil { + return errors.New("nil logger") + } + o.logger = logger + return nil +} + +func (o *PassthroughAuthorizer) Close() error { + return nil +} + +func (o *PassthroughAuthorizer) GetGinHandler(role string) gin.HandlerFunc { + return func(c *gin.Context) { + o.logger.Debugw("passthrough", "path", c.Request.URL.Path) + } +} diff --git a/auth/problem.go b/auth/problem.go new file mode 100644 index 00000000..51e5dee9 --- /dev/null +++ b/auth/problem.go @@ -0,0 +1,22 @@ +package auth + +import ( + "strings" + + "github.com/gin-gonic/gin" + "github.com/moogar0880/problems" + "github.com/veraison/services/log" +) + +func ReportProblem(c *gin.Context, status int, details ...string) { + prob := problems.NewStatusProblem(status) + + if len(details) > 0 { + prob.Detail = strings.Join(details, ", ") + } + + log.LogProblem(log.Named("api"), prob) + + c.Header("Content-Type", "application/problem+json") + c.AbortWithStatusJSON(status, prob) +} diff --git a/auth/roles.go b/auth/roles.go new file mode 100644 index 00000000..543bb701 --- /dev/null +++ b/auth/roles.go @@ -0,0 +1,7 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package auth + +var NoRole = "" +var ManagerRole = "manager" +var ProvisionerRole = "provisioner" diff --git a/deployments/docker/Makefile b/deployments/docker/Makefile index 3ca4a547..3a1f512e 100644 --- a/deployments/docker/Makefile +++ b/deployments/docker/Makefile @@ -27,6 +27,8 @@ vts_FLAGS := -v $(STORES_VOLUME):/opt/veraison/stores management_FLAGS := -v $(STORES_VOLUME):/opt/veraison/stores -p $(MANAGEMENT_PORT):$(MANAGEMENT_PORT) provisioning_FLAGS := -p $(PROVISIONING_PORT):$(PROVISIONING_PORT) verification_FLAGS := -p $(VERIFICATION_PORT):$(VERIFICATION_PORT) +keycloak_FLAGS := -p $(KEYCLOAK_PORT):$(KEYCLOAK_PORT) -e KEYCLOAK_ADMIN=admin \ + -e KEYCLOAK_ADMIN_PASSWORD=admin ifneq ($(DEBUG_PORT),) DEBUG_PORT_FLAG := -p $(DEBUG_PORT):$(DEBUG_PORT) @@ -86,8 +88,8 @@ services: @# image targets (possibly because of the need to recursively resolve %, @# but I haven't looked too much into it). Recursively calling $(MAKE) here @# resolves the issue. - $(MAKE) .built/vts-container .built/provisioning-container .built/verification-container \ - .built/management-container + $(MAKE) .built/keycloak-container .built/vts-container .built/provisioning-container \ + .built/verification-container .built/management-container .PHONY: vts vts: deploy .built/vts-container @@ -113,6 +115,13 @@ management: deploy .built/management-container .PHONY: management-image management-image: deploy .built/management-image +.PHONY: keycloak +keycloak: deploy .built/keycloak-container + +.PHONY: keycloak-image +keycloak-image: deploy .built/keycloak-image + + .PHONY: network network: .built/network @@ -173,11 +182,12 @@ docker-clean: docker volume rm -f $(DEPLOY_DEST); \ fi @# -f ensures exit code 0, even if image doesn't exist - docker container rm -f vts-service provisioning-service verification-service management-service + docker container rm -f keycloak-service vts-service provisioning-service \ + verification-service management-service docker volume rm -f veraison-logs veraison-stores @# ubuntu uses an older version of docker without -f option for network; hence the || : cludge docker network rm $(VERAISON_NETWORK) || : - docker image rm -f veraison/builder veraison/vts veraison/provisioning \ + docker image rm -f veraison/builder veraison/keycloak veraison/vts veraison/provisioning \ veraison/verification veraison/manager rm -rf .built @@ -188,6 +198,7 @@ host-clean: rm -rf $(DEPLOY_DEST); \ fi rm -rf $(CONTEXT_DIR) + rm -f .built/deploy .PHONY: really-clean really-clean: clean docker-clean host-clean diff --git a/deployments/docker/deployment.cfg b/deployments/docker/deployment.cfg index 3bdd2077..f38a7676 100644 --- a/deployments/docker/deployment.cfg +++ b/deployments/docker/deployment.cfg @@ -13,6 +13,7 @@ VTS_PORT=50051 PROVISIONING_PORT=8888 VERIFICATION_PORT=8080 MANAGEMENT_PORT=8088 +KEYCLOAK_PORT=11111 # Deploy destination is either an absolute path to a directory on the host, or # the name of a docker volume. diff --git a/deployments/docker/src/builder-dispatcher b/deployments/docker/src/builder-dispatcher index 74175878..e37e8f63 100755 --- a/deployments/docker/src/builder-dispatcher +++ b/deployments/docker/src/builder-dispatcher @@ -37,6 +37,7 @@ function deploy() { cp $BUILD_DIR/scheme/bin/* $DEPLOY_DIR/plugins/ cp $BUILD_DIR/deployments/docker/src/skey.jwk $DEPLOY_DIR/ cp $BUILD_DIR/deployments/docker/src/service-entrypoint $DEPLOY_DIR/ + cp $BUILD_DIR/deployments/docker/src/veraison-realm.json $DEPLOY_DIR/ cp $gobin/evcli $DEPLOY_DIR/utils/ cp $gobin/cocli $DEPLOY_DIR/utils/ cp $gobin/pocli $DEPLOY_DIR/utils/ @@ -46,6 +47,7 @@ function deploy() { source $BUILD_DIR/deployments/docker/deployment.cfg set +a cat $BUILD_DIR/deployments/docker/src/config.yaml.template | envsubst > $DEPLOY_DIR/config.yaml + cat $BUILD_DIR/deployments/docker/src/keycloak.conf.template | envsubst > $DEPLOY_DIR/keycloak.conf echo "initializing stores" for t in en ta po diff --git a/deployments/docker/src/config.yaml.template b/deployments/docker/src/config.yaml.template index 717ab91d..b6a46a19 100644 --- a/deployments/docker/src/config.yaml.template +++ b/deployments/docker/src/config.yaml.template @@ -35,4 +35,8 @@ po-store: datasource: stores/vts/po-store.sql po-agent: backend: opa +auth: + backend: keycloak + host: keycloak-service + port: 11111 # vim: set ft=yaml: diff --git a/deployments/docker/src/keycloak.conf.template b/deployments/docker/src/keycloak.conf.template new file mode 100644 index 00000000..cb830852 --- /dev/null +++ b/deployments/docker/src/keycloak.conf.template @@ -0,0 +1,5 @@ +# See https://www.keycloak.org/server/all-config for all alvailable configuration. +http-enabled=true +http-port=${KEYCLOAK_PORT} +hostname-strict=false + diff --git a/deployments/docker/src/keycloak.docker b/deployments/docker/src/keycloak.docker new file mode 100644 index 00000000..44c170d9 --- /dev/null +++ b/deployments/docker/src/keycloak.docker @@ -0,0 +1,17 @@ +FROM quay.io/keycloak/keycloak:22.0.1 as keycloak-builder + +WORKDIR /opt/keycloak +# note: for development set up early; use proper certification in production. +RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 \ + -dname "CN=server" -alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" \ + -keystore conf/server.keystore + +RUN /opt/keycloak/bin/kc.sh build + +FROM quay.io/keycloak/keycloak:22.0.1 +COPY --from=keycloak-builder /opt/keycloak/ /opt/keycloak/ +COPY keycloak.conf /opt/keycloak/conf/keycloak.conf +COPY veraison-realm.json /opt/keycloak/data/import/veraison-realm.json + +ENTRYPOINT ["/opt/keycloak/bin/kc.sh"] +CMD ["start", "--optimized", "--import-realm"] diff --git a/deployments/docker/src/load-config.mk b/deployments/docker/src/load-config.mk index d074c7e8..24a33c45 100644 --- a/deployments/docker/src/load-config.mk +++ b/deployments/docker/src/load-config.mk @@ -11,6 +11,7 @@ VTS_PORT ?= 50051 PROVISIONING_PORT ?= 8888 VERIFICATION_PORT ?= 8080 MANAGEMENT_PORT ?= 8088 +KEYCLOAK_PORT ?= 11111 # Deploy destination is either an absolute path to a directory on the host, or # the name of a docker volume. diff --git a/deployments/docker/src/veraison-realm.json b/deployments/docker/src/veraison-realm.json new file mode 100644 index 00000000..0fd76cb4 --- /dev/null +++ b/deployments/docker/src/veraison-realm.json @@ -0,0 +1,1841 @@ +{ + "id" : "f1c336ca-84a0-4bbf-b075-d6276bfb8f5e", + "realm" : "veraison", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 1800, + "accessTokenLifespanForImplicitFlow" : 1800, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "3c14c82e-ef6e-4c35-8fc5-b95322e12698", + "name" : "manager", + "description" : "Manages policies.", + "composite" : false, + "clientRole" : false, + "containerId" : "f1c336ca-84a0-4bbf-b075-d6276bfb8f5e", + "attributes" : { } + }, { + "id" : "29a6925f-81e8-4177-bb95-3c36bf4b7645", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "f1c336ca-84a0-4bbf-b075-d6276bfb8f5e", + "attributes" : { } + }, { + "id" : "03e833d2-8f65-4e1d-848a-87a01650b4d5", + "name" : "provisioner", + "description" : "Provisions trust anchors and endorsements.", + "composite" : false, + "clientRole" : false, + "containerId" : "f1c336ca-84a0-4bbf-b075-d6276bfb8f5e", + "attributes" : { } + }, { + "id" : "3c85b41b-2cd1-40af-9e31-c6a9d24114ce", + "name" : "default-roles-veraison", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "view-profile", "manage-account" ] + } + }, + "clientRole" : false, + "containerId" : "f1c336ca-84a0-4bbf-b075-d6276bfb8f5e", + "attributes" : { } + }, { + "id" : "a40e1662-6ddb-4fd7-829a-ffece56b48d2", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "f1c336ca-84a0-4bbf-b075-d6276bfb8f5e", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "66fc745f-b46a-4efa-bd17-77e060d7b2a0", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "b0d0efb7-3691-4ead-b33a-f16191bc5789", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "view-realm", "view-authorization", "view-clients", "manage-identity-providers", "query-clients", "query-groups", "manage-events", "create-client", "manage-users", "manage-clients", "view-users", "view-identity-providers", "manage-realm", "manage-authorization", "query-realms", "impersonation", "query-users", "view-events" ] + } + }, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "a34cb720-d7c5-408b-8b52-ed426d6d809c", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "44017973-fe85-45b5-b7f5-f53a757bce73", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "00eb870e-9ca7-4933-8809-4c536c1a22f6", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "c1fa45d6-dd02-4487-9cc2-6293f825467b", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "0d5c3ba5-7763-472c-9239-f946931e413f", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "fcc850bb-f082-4dd6-b5ab-510d1cc89311", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "2b8e4b72-1c63-4528-9be5-271868c1372a", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "bcf9a96e-d875-4548-adb0-8768404e54f7", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "a9cb8f89-fec3-4679-a0fc-3557cacc8c0f", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "9186b791-8864-4373-85d4-f27a4895ec9b", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-groups", "query-users" ] + } + }, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "67eeb9b5-6368-4489-9606-d04a7c78e330", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "50da5487-88c2-4c51-b95c-a7249e283df2", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "aca9a2d7-f1bc-4b2e-827d-d366fdfce45a", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "93cdc235-c527-4818-9601-3f27503a5988", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "41419687-e0a3-4a1b-a380-8a200f2ae2a8", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "66c8d39f-46de-4861-934e-440998d5e427", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + }, { + "id" : "50d9bd9d-d100-48be-8239-2661a6a13ca7", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "4882f437-a423-46a1-878c-10616b7d6117", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "cdeea646-d366-479d-b2d2-40853d6d363d", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "e9f5fb23-6688-4a97-897c-fe9f6a370c64", + "attributes" : { } + } ], + "account" : [ { + "id" : "0b90f930-8203-4599-be96-e5a5cac8ee7b", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "b8302e03-6c05-4a42-85e9-a46370d2b76b", + "attributes" : { } + }, { + "id" : "2c7d0710-a3d8-4a40-9f2e-8143f2e9e153", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "b8302e03-6c05-4a42-85e9-a46370d2b76b", + "attributes" : { } + }, { + "id" : "d3434c63-75fc-4ac2-af54-91ce26ace472", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "b8302e03-6c05-4a42-85e9-a46370d2b76b", + "attributes" : { } + }, { + "id" : "7846f314-a5f3-4bac-a02c-7da437c300ce", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "b8302e03-6c05-4a42-85e9-a46370d2b76b", + "attributes" : { } + }, { + "id" : "2f69f35f-6748-4002-aecf-85d194c28d94", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "b8302e03-6c05-4a42-85e9-a46370d2b76b", + "attributes" : { } + }, { + "id" : "1edba5fd-9d7e-4a15-8dcf-3a14a4b738cc", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "b8302e03-6c05-4a42-85e9-a46370d2b76b", + "attributes" : { } + }, { + "id" : "ce9b2c54-d838-42ae-bbe6-d6ee70e46902", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "b8302e03-6c05-4a42-85e9-a46370d2b76b", + "attributes" : { } + }, { + "id" : "4df2f06c-a286-4f29-bb5e-c5259780099c", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "b8302e03-6c05-4a42-85e9-a46370d2b76b", + "attributes" : { } + } ], + "veraison-client" : [ ] + } + }, + "groups" : [ ], + "defaultRole" : { + "id" : "3c85b41b-2cd1-40af-9e31-c6a9d24114ce", + "name" : "default-roles-veraison", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "f1c336ca-84a0-4bbf-b075-d6276bfb8f5e" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName", "totpAppFreeOTPName" ], + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "users" : [ { + "id" : "778a3424-b538-415c-b9c6-ade0e483d818", + "createdTimestamp" : 1692274136073, + "username" : "veraison-manager", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "firstName" : "", + "lastName" : "", + "credentials" : [ { + "id" : "086eb891-30fd-408d-91a0-7d4c8a701f54", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1692274151671, + "secretData" : "{\"value\":\"KDJZv/qvMYMCb5v18ymqGrKy9ZPk/3zB3WvrzwFogRI=\",\"salt\":\"zU0ayGQFxfgyU2T2EU90+w==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "manager", "default-roles-veraison" ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "3d0bfa21-50b0-496a-9a22-c6e1f63238f9", + "createdTimestamp" : 1692274044033, + "username" : "veraison-provisioner", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "firstName" : "", + "lastName" : "", + "credentials" : [ { + "id" : "0dd61192-932a-4972-a168-2c4867673396", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1692275392983, + "secretData" : "{\"value\":\"+uFdoOr+hk62Z87HGA9RvWcXhJMNX4YHPjmkjJSK16U=\",\"salt\":\"s/dmj1YbJ+/yLdbnmAg/8Q==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "provisioner", "default-roles-veraison" ], + "notBefore" : 0, + "groups" : [ ] + } ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "b8302e03-6c05-4a42-85e9-a46370d2b76b", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/veraison/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/veraison/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "0124f2b3-be1b-49b5-a113-352d1ffe299a", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/veraison/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/veraison/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "3b63fc08-5779-4dc5-a2ce-5a22a201fd49", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "b2e061bf-883b-4534-925b-f3aafb433fd7", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "e9f5fb23-6688-4a97-897c-fe9f6a370c64", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "4882f437-a423-46a1-878c-10616b7d6117", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "9fc0fda8-3c5e-46d4-ae64-126fa11ecbb3", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/veraison/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/veraison/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "7bd436f6-4a54-425e-bb6c-7de806fcf84d", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "30ea0b69-30fe-46bd-82f7-ac1f980b6928", + "clientId" : "veraison-client", + "name" : "", + "description" : "", + "rootUrl" : "", + "adminUrl" : "", + "baseUrl" : "", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "YifmabB4cVSPPtFLAmHfq7wKaEHQn10Z", + "redirectUris" : [ "/*" ], + "webOrigins" : [ "/*" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "oidc.ciba.grant.enabled" : "false", + "client.secret.creation.time" : "1692267068", + "backchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "+", + "oauth2.device.authorization.grant.enabled" : "false", + "display.on.consent.screen" : "false", + "backchannel.logout.revoke.offline.tokens" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "cdb2454d-355f-46e6-8c75-5ee7d104011d", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${profileScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "5a0ee988-c3b5-400d-a77a-b6db95b44583", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "57f0aa40-359f-4f54-8413-1c414d95dc62", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "2fb49e8e-fceb-4ebf-a4f8-b7b074d076fb", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "fe6d8736-c643-4189-bff0-22179ccd0173", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "1407f674-94da-4baf-85a7-4825673df97a", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "7d79f487-aa57-414e-b68f-b731056aef2a", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "d2296431-072a-4bf1-87be-6e0f390e255a", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "162ede80-8a96-47ae-9671-0c6eb1bfe520", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "c93b9d49-5826-45ac-b05a-c4c5fafa5b96", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "e7cf775c-3af5-4ae4-aaf4-85a84999d064", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "15c2e269-faff-4013-a68d-ed20ea46031e", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "70ad35eb-7736-487d-b4c0-42fd4fe0d563", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "b4b48ad4-f324-47d9-a0dc-7d5a47c5126d", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "ed92a954-3a49-4cda-ad97-c5c605639344", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "622a036e-ec1b-4591-8304-38f6383d5405", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "a53cf6f7-1ecc-403d-9574-f0691388e401", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false", + "consent.screen.text" : "" + }, + "protocolMappers" : [ { + "id" : "798ddcba-546b-452d-9d15-27cd5b12b1be", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "801544eb-e426-478f-b9f1-d9292d521147", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${addressScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "7461381f-3dbf-49d7-b033-619b0148ca09", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "01d158bf-8c4f-4b3d-b84a-141e114c0b8f", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "4eb1eb4a-5f27-434b-a141-a9f6902d93d5", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "3f241149-6525-4a09-89da-649094ab0e47", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${emailScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "43909b68-6505-4119-abd9-df8447ab6dfc", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "1caf557c-c8c4-4e5e-bf42-20365baa40e1", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "c8fa1b3b-d9ef-4fef-b291-35318d6817ad", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${rolesScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "e1fbb06d-9b7f-4e2f-ae8c-a2d0cd41dc76", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "b3875b32-f72d-4fed-ac28-fe48b94261bf", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "e9094f82-a0ed-47e2-b484-377e55e24d68", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "23735c41-4b70-46b0-8bd8-66d6db6ae200", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "4d7eb99e-09bb-450f-9f22-324d52dc58da", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "70c1a038-13c6-405a-8e90-ea23930a7ff3", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "e24a3896-7bc6-4007-9582-852cf918aaab", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "consent.screen.text" : "${phoneScopeConsentText}" + }, + "protocolMappers" : [ { + "id" : "523c6a94-87a6-4022-bdbf-842b2b2a2ac4", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "4adfae7f-d902-467f-91a0-22bbfa3b845f", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "a867d920-baaf-424b-8e56-8c941f79bbd9", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "da651414-ffb1-4e47-a2ca-cdd1c6270481", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "838db39f-26e9-49f4-a032-522e8a817212", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "5516ee07-cd4c-40c2-8ee0-c39010f6c521", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper" ] + } + }, { + "id" : "961ce4e7-198c-49bc-b988-6b87f313b89b", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "ad3a8ca5-9bd9-4c97-a7c7-ec04a2ad6279", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "875ae783-21e0-4971-8643-e0d9dfbce3e1", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper" ] + } + }, { + "id" : "4bce5375-223e-41cd-af02-1d3813895b07", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "23ace297-c838-4fa3-ae94-3f0e01d257d5", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "69da926b-3bb5-433c-80bd-906a4180a8bf", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "94bc520a-7994-42a9-8083-3c185e66eca8", + "name" : "hmac-generated", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "a7e63c45-88cb-4a77-a458-a55473909d17" ], + "secret" : [ "tbWy-FjQ4PPZClLRCByN5PAWOh5oHwlPjJklDF-WyYpr4WCQnfBnUPnBdEI6ThyGEZ1oSGEq3wnivoQMARYLOA" ], + "priority" : [ "100" ], + "algorithm" : [ "HS256" ] + } + }, { + "id" : "ea51c427-9158-4564-ad9c-fccff828ea2a", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpQIBAAKCAQEAwQ1evDFc9jC1e6SZuM4oAVs+O1Mj8iXK6otj4aYG5ox2I9Vg2AjaF53oX0b9PIUyvkdxpPYBPIqkfe7P6woXjOZEiUo/vQfR79qdjwjfWLhOGuLezj2OQjAEzRdg4A2LhMDYv8rc5S3aTJ1Q7hdXe+ETDg6o/2ou/IJyIXcz9HDnIelrtPTemmQbrSFvYh8NLJwLqLfa4KxCVnUiwOGWed6L96lV8Z6W+R7ssN/XekaPf8fsfxXn4DFU7mGHEpjEfInUHg7oQMSFotCbDFg1JFKLRIFOZvo3690cgdf/VaeWvYuc5WEK+hEHq2POXIYTpUtNndGasdKQBnhY88ZZqwIDAQABAoIBAAWMOCVkWG2NZ3RkKIlnsz1GvU/AdeSm3WFGxtoHMWToCG/DLq3xTdh75d+cZKPxQqIL34zZXPO7xY+w5dy1RN6nB1/X+GOa7peGoBx/fsZH6vFZhQRG6ehRBPJNMlrTSNJ0evDCsv0LB55IBGQIhlpoVL8vVf/xBzn5GFqJ2GkKgMdUTVeLIOt48V/raoN4lS0g8jJMypOorXs17JLPKsECOFm4VKKzYQ/S5JHi/Cq9PcSex+V2nD/blh5FqbVWYxGP5hFPHx0N37Hi8fxZAXAT8mHShNyT7NnPsdgy6kEJ09tET9KP3A7ymlzXD8BXoWEKBMvvepfyle4GTNxDuuECgYEA46+2yQaAINnKMzFcI4Smk8H8vLM3lXl5bXWBqYTx4FeB0VOsLBn46IOu8pxJw2nx2htPmKUDkcQZO0NqOz3MTIxVgjwidRnzmp3p0K4GDeKdnO7KGyDxScQxqNl4zuQc/l15YfQN09jBxt6WtjF+qXUV6qGiNHGkQFxMRF4T6LMCgYEA2Q8YLIjgJybWsHQ7fvHNNMQjQGGRAdSWRHJLfpJU/icfjrLCB/3i+Pc17UmovRtJIYUzHVs54rAB5tl7yNc7g0XMhTCxCV/0ETPYlKL3Yb18aZvSvwp7R3Nxru/y1l/E1R9vyF8bwzVthe2Any53Ru/cJhhUknXZPHYI4++aFykCgYEA1kI8V9/uIvvP82y3sBTcTJ94HnroC5lMU10Ir2WT1/GBEGMU2kt2mBeTQmsgXuwL05tvw81FFp7av5IpHaaB4mcM8Il2Q4wwWYfQx7d7qwVeHJf5SJ4vcaNWt/YuYUL4pcWAvFTVzk1jzKkaWkkpUH4GKc4AvilSz94LRyrgwVECgYEArAnytxml0GJQN3tozK0KYJA1AIpUTIcasxWEEMYa53ZK5Od6MqtggsQt0e1X+Mrvo8nXQaVUs/+dAkPOgNlXKizgdZCqQSv0Xs4hE243dRiiy3HeD91W6MLvkCBO8OrnL6TDDKWVc5udO1GLaJ+Dmo3yh58xKQSPMgS79y2pjEECgYEA2yjd8vfqRVcsnz6LimX2YIMIlRYd867de4sEIhJHSfXspxD5UgVj9cxn6BUvzAafFH1cFV+LR7DU4IjgBsWsYWYq1Kt1SXrrxwh7LmYT6PX2z7jfk6jRcpsZPNJdCDqmW03Ex+xLGUKKj7C5vMhe/fZqV383rGTKPuO81AVxjKI=" ], + "certificate" : [ "MIICnzCCAYcCBgGKA2Q2rTANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAh2ZXJhaXNvbjAeFw0yMzA4MTcxMjA1MTNaFw0zMzA4MTcxMjA2NTNaMBMxETAPBgNVBAMMCHZlcmFpc29uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQ1evDFc9jC1e6SZuM4oAVs+O1Mj8iXK6otj4aYG5ox2I9Vg2AjaF53oX0b9PIUyvkdxpPYBPIqkfe7P6woXjOZEiUo/vQfR79qdjwjfWLhOGuLezj2OQjAEzRdg4A2LhMDYv8rc5S3aTJ1Q7hdXe+ETDg6o/2ou/IJyIXcz9HDnIelrtPTemmQbrSFvYh8NLJwLqLfa4KxCVnUiwOGWed6L96lV8Z6W+R7ssN/XekaPf8fsfxXn4DFU7mGHEpjEfInUHg7oQMSFotCbDFg1JFKLRIFOZvo3690cgdf/VaeWvYuc5WEK+hEHq2POXIYTpUtNndGasdKQBnhY88ZZqwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBykUMKuwsNci+zvJdnRPINREzBxVJGbpwJTYdWT8dNYHLakP0HhRXDqAzWdBQkuqSTroUjCSxKsLfnoon3dAQkYM/64yCHFo13a1wtg0pJw0o8BTM5j2TrjnFWvj7XR0xiRGpj2yz0as4gN29Gj3Atw1IEkBnSO4BySEyTou7fGEqWcE6OUWAeyA5OCuCs7geSbghNNCrCMnyKAGLIgidGI7CgopZMQqJ/UoiPXHNuor+N3TMiDYZ3knsi9jY8bJ1DixbkjOVmaG3VI7z5iwNF7jrOpY7XR9SMKt32QFxf1VXMGzygiBoRPIpt6OENGO4naJTIAVLwaZxaUxClOz+G" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "af7c0bdc-1ea0-4c0a-9a95-035e49102c23", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEApJR7Ia2PCedukU9Rsb4CPlzs4UNiAIk5ou/4bTLEMQx8Cb6P4UNJoG95Wpf8jsaZtRUVei+SB7BWLfcPi4w7x6JRhrS67vOxH+P4BbtMqdg5aID/fCxUTMJMiSWx9R39PC6mFzc4ZvxzBvNmIbBBiFWuIHeYZOR/3lcrLTh1r9pCYywRzGPbY0u/8CWDxaQJGrNSGjnEXqL97TTuMuu2t07XJAglPsJR63fxOqgd5RET39uDHQqbLy7bxNiCq9+vMcF6JTr13KYDHmtb4qPT95zoqhRHrCI7EI+ruozpoQG6OV/IOXGJhYyFBjMZLCOIMWWoX510QPmQyRNwHsUT3wIDAQABAoIBABpmLXYSe+jlHumKD/BIf92hqZGHQyITi5OPld7A5OglONmV9UhMiHPb+FryxDKMDWhsMFzADVbUZI53nHO66R/gRomAAKLcTg4afX9AiE86KBiTkJJKHgo4pG7fWrNBveFjGNs3zVafqefJ2m5kR668Z5488MybkR3eB5v3ptEFGgU2ruCFbcLSLM0Rg5MwvXuxNrBFLLvQFsAatofFuDEcGHEXX6Rc+3WY8zK/rvFPhB1O2hCuoj+Ka2NcjEQgetlK4KKYBm42mwV0vqNE6t8ewR3+MvJnr6fWOfTlqR+UQ4BbPEteXrV8Hwi4hSAWrrr9MZgBBzUeKroSHgsAUrkCgYEAzhDK4FQCuEGCoEFVKGGD996qXI1j+yLXC4QrIqHyu8Al+GNpJwVgsUWsF9MfhVNjtctBb2qkIdwfV3nlAA2Sg8gHSDVWA5hAgK8d05qCj3aEuxk/xkb2naluuAvztycVaub7Wn54hq8pY3wOifgMl4Qz+7FoAxFNv8HAgrgIJxUCgYEAzHYk7VXjmiTY9DEXXnd6SugVZu5WTSqykPB7nFfosIIdgi0uInFHdhDMdHR97dW7GZkMB1p8zoVdSrJaU6iiDelfbXFX69tQtPbi3eVqls5htUuvozCNyz7eeZnHQ2EKsxpIZC+OOWHTJbUtGDYRG6yvOqqGR1ZTUNUB9g03zCMCgYB8ABTlKwi78gf2AXqKIywzo1Um/ppUjGGVd4Ixg/y6SGVQ9BlZtt25rzBg4dXM+CI/SkFlF2oPShO+IwbPolsxW9Qt+pJ49UyTY01ygT7hr7Mtl4MOALP0qfmLXP3aj/VOcBJ/IS3L9mnUiNmC4rZJEu/pHJd3iRkdNC1xO+cEBQKBgQCGenqFQ8Wkn/G2gwds0ca0t/tDrSVEMf4qyJF03nkkhyAje9XpP3qSFDB1tB0Trk0WZAx+VazbJOqcc7xnY/XakpF6aV87uQ9XRz8mVXuK3wly9en6ure4Y4xujI98KLqh3HqaspCn+0imd4jGcOFFw4mpW3lgOE4qTz+v9zeo4wKBgQCNdhsXJs1gGwgwr5cyUA6XWvoXt5XaQPbSDe8g1h5Va4K28aU3Z+FoDrq6xCkb7klk38S0grQmykGXnVKw/6R09I3WRwe7U9OGjxiN99jDG/wc0qIxdW2XxlvPgphi4AR/XbD16hvhVHecL3p4frjJAu2kp7GFieNcVFPjtcQm2g==" ], + "certificate" : [ "MIICnzCCAYcCBgGKA2Q3iTANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAh2ZXJhaXNvbjAeFw0yMzA4MTcxMjA1MTRaFw0zMzA4MTcxMjA2NTRaMBMxETAPBgNVBAMMCHZlcmFpc29uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApJR7Ia2PCedukU9Rsb4CPlzs4UNiAIk5ou/4bTLEMQx8Cb6P4UNJoG95Wpf8jsaZtRUVei+SB7BWLfcPi4w7x6JRhrS67vOxH+P4BbtMqdg5aID/fCxUTMJMiSWx9R39PC6mFzc4ZvxzBvNmIbBBiFWuIHeYZOR/3lcrLTh1r9pCYywRzGPbY0u/8CWDxaQJGrNSGjnEXqL97TTuMuu2t07XJAglPsJR63fxOqgd5RET39uDHQqbLy7bxNiCq9+vMcF6JTr13KYDHmtb4qPT95zoqhRHrCI7EI+ruozpoQG6OV/IOXGJhYyFBjMZLCOIMWWoX510QPmQyRNwHsUT3wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA/zFEkpuhffnzxjKRZU05qjA0fEOp7t8C6YI0ERd+bhp0wS5thicSCPhTZgIe3RH23Ozd8lbsmBpnqsOg2PGw/mVjFcutTZrHObCYmmAIQBYlfwfC4UqIx5UjjY+3H/mdsS9XsUAtR9OIuK8NBTSZqHTIK56ifYuifvpVUVbotqM+Yg4r1D6lPBwsvo9OdRxKODrEocfwmMjUJfNX4p1ywXE8sazlBm0CbHASugLEBRU5fQAig+D6RyEijPBXKGrVJxXOwEGO/nukkgmPYruPwNy+h3WmURwkQ5IbnvTBacu9iTYMN/vaRRq1imHNWnjFbZnDCJr+TWIPbVQjY6FY4" ], + "priority" : [ "100" ] + } + }, { + "id" : "1351fa5d-2a4a-4780-8fae-d3927b012acb", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "b3f2725f-76e5-4f0d-9b86-328a2995ef85" ], + "secret" : [ "CC51rVYsGuwM7wnkjylqcA" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "2015fa69-e7e2-47f2-85a9-72083f2d5799", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "c33ebc38-f2c7-4f51-a53e-1ba9370f670a", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "edb6e773-66f4-4c5d-96e8-72161fd4aca9", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "22dda1be-7bc3-4bd9-9792-d54b114e7273", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "cbf77531-658d-4b21-bce9-341e21357133", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "5df209de-e792-43dd-8fd8-ca9462d5c3d7", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "26fccf30-44b5-43a6-8bea-621d74ad48c4", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "a77263c0-036d-4b0f-a749-adf9bc01e32a", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "e62b015b-62cf-44e9-aaef-b782278d1f94", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "d9f9ab6a-a6ae-4f8f-a411-8dd0a9a11a46", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "b63e66d7-e30d-4458-b56c-e3d10c48ad9a", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "20dd3a17-943d-40a4-a320-b78a018c7740", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "8dc9f372-c43c-48c3-80a4-f7fccb183a10", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "2a615cce-f472-44dc-a379-c7b28ac78e2b", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "e929e547-123a-4d1d-9d53-da597fda78ce", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "f2166939-6982-468f-9be9-0217f35c2386", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-profile-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "6db0cf80-5f81-4bcf-8e0b-3c4a682995eb", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "8afa88d2-dbd0-4e51-bf01-3032c1221d34", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "629cf0f7-f153-4f85-9bfe-2f5c78491119", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "2ee0fe91-8964-4c02-982a-f3640f0fa9de", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "TERMS_AND_CONDITIONS", + "name" : "Terms and Conditions", + "providerId" : "TERMS_AND_CONDITIONS", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaExpiresIn" : "120", + "cibaAuthRequestedUserHint" : "login_hint", + "oauth2DeviceCodeLifespan" : "600", + "clientOfflineSessionMaxLifespan" : "0", + "oauth2DevicePollingInterval" : "5", + "clientSessionIdleTimeout" : "0", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "clientOfflineSessionIdleTimeout" : "0", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false" + }, + "keycloakVersion" : "22.0.1", + "userManagedAccessAllowed" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} diff --git a/deployments/docker/veraison b/deployments/docker/veraison index cb8c213e..5f686b2d 100755 --- a/deployments/docker/veraison +++ b/deployments/docker/veraison @@ -9,20 +9,24 @@ function status() { local prov=$(_get_container_state provisioning-service) local verif=$(_get_container_state verification-service) local manage=$(_get_container_state management-service) + local keycloak=$(_get_container_state keycloak-service) if [[ $_quiet == true ]]; then local vts=$(_strip_color $vts) local prov=$(_strip_color $prov) local verif=$(_strip_color $verif) local manage=$(_strip_color $manage) + local keycloak=$(_strip_color $keycloak) local status="${_yell}stopped${_reset}" - if [[ "$vts" == "running" || "$prov" == "running" || "$verif" == "running" || "$manage" == "running" ]]; then + if [[ "$vts" == "running" || "$prov" == "running" || "$verif" == "running" || \ + "$manage" == "running" || "$keycloak" == "running" ]]; then status="${_yell}partial${_yell}" fi - if [[ "$vts" == "running" && "$prov" == "running" && "$verif" == "running" && "$manage" == "running" ]]; then + if [[ "$vts" == "running" && "$prov" == "running" && "$verif" == "running" && \ + "$manage" == "running" && "$keycloak" == "running" ]]; then status="${_green}running${_reset}" fi @@ -32,6 +36,7 @@ function status() { echo -e "provisioning: $prov" echo -e "verification: $verif" echo -e " management: $manage" + echo -e " keycloak: $keycloak" fi } @@ -39,6 +44,7 @@ function start() { local what=$1 if [[ "x$what" == "x" ]]; then + start_keycloak start_vts sleep 0.5 # wait for vts to start before starting the services that depend on it. start_provisioning @@ -52,6 +58,8 @@ function start() { start_verification elif [[ "$what" == "management" || "$what" == "management-service" ]]; then start_management + elif [[ "$what" == "keycloak" || "$what" == "keycloak-service" ]]; then + start_keycloak else echo -e "$_error: unknown service: $what" exit 1 @@ -66,6 +74,7 @@ function stop() { stop_verification stop_provisioning stop_vts + stop_keycloak elif [[ "$what" == "vts" || "$what" == "vts-service" ]]; then stop_vts elif [[ "$what" == "provisioning" || "$what" == "provisioning-service" ]]; then @@ -74,6 +83,8 @@ function stop() { stop_verification elif [[ "$what" == "management" || "$what" == "management-service" ]]; then stop_management + elif [[ "$what" == "keycloak" || "$what" == "keycloak-service" ]]; then + stop_keycloak else echo -e "$_error: unknown service: $what" exit 1 @@ -91,6 +102,8 @@ function follow() { follow_verification elif [[ "$what" == "management" || "$what" == "management-service" ]]; then follow_management + elif [[ "$what" == "keycloak" || "$what" == "keycloak-service" ]]; then + follow_keycloak else echo -e "$_error: unknown service: $what" exit 1 @@ -145,6 +158,18 @@ function follow_management() { docker container logs --follow --timestamps management-service } +function start_keycloak() { + docker container start keycloak-service +} + +function stop_keycloak() { + docker container stop keycloak-service +} + +function follow_keycloak() { + docker container logs --follow --timestamps keycloak-service +} + function manager() { docker container run --rm -t \ --network veraison-net \ diff --git a/go.mod b/go.mod index d41d353c..eb723323 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.8.4 + github.com/tbaehler/gin-keycloak v1.5.0 github.com/veraison/ccatoken v1.1.0 github.com/veraison/cmw v0.1.0 github.com/veraison/corim v1.0.0 @@ -34,19 +35,24 @@ require ( github.com/veraison/parsec v0.1.0 github.com/veraison/psatoken v1.2.0 go.uber.org/zap v1.23.0 - golang.org/x/text v0.9.0 + golang.org/x/text v0.12.0 google.golang.org/grpc v1.53.0 google.golang.org/protobuf v1.30.0 + gopkg.in/square/go-jose.v2 v2.6.0 ) require ( github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/golang/glog v1.0.0 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect + golang.org/x/oauth2 v0.4.0 // indirect + google.golang.org/appengine v1.6.7 // indirect ) require ( @@ -99,10 +105,10 @@ require ( github.com/yashtewari/glob-intersection v0.1.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect - golang.org/x/crypto v0.9.0 // indirect + golang.org/x/crypto v0.12.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.8.0 // indirect + golang.org/x/sys v0.11.0 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index c30a0e2f..bd70ad09 100644 --- a/go.sum +++ b/go.sum @@ -422,6 +422,7 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -458,12 +459,19 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -681,6 +689,7 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -711,11 +720,14 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= @@ -886,6 +898,8 @@ github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuh github.com/opencontainers/selinux v1.10.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -899,6 +913,7 @@ github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go. github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -958,7 +973,9 @@ github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqn github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1057,6 +1074,8 @@ github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNG github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tbaehler/gin-keycloak v1.5.0 h1:ozzuIv5PNNxsNGFb7KCD40smoKs2EwTGAftcstwvWdI= +github.com/tbaehler/gin-keycloak v1.5.0/go.mod h1:2zAN7is0lTIBP6Ic9VSOH1RggdYtoUlorWxsmGqBr4Y= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1065,7 +1084,11 @@ github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -1224,10 +1247,13 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1327,6 +1353,7 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= @@ -1348,6 +1375,9 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1476,6 +1506,7 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1485,6 +1516,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1504,6 +1537,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1622,6 +1657,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -1766,6 +1802,8 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/integration-tests/docker/bashrc b/integration-tests/docker/bashrc index 34e3d3c1..f4d1409b 100644 --- a/integration-tests/docker/bashrc +++ b/integration-tests/docker/bashrc @@ -3,6 +3,7 @@ export PYTHONPATH=$PYTHONPATH:/integration-testing/utils export PROVISIONING_HOST=provisioning-service export VERIFICATION_HOST=verification-service export MANAGEMENT_HOST=management-service +export KEYCLOAK_HOST=keycloak-service export PS1='\e[0;32m\u@debug-container \e[0;34m\w\n\e[0;32m$\e[0m ' alias ll='ls -lh --color=auto' diff --git a/integration-tests/tests/common.yaml b/integration-tests/tests/common.yaml index 75ad5ca5..958a0ceb 100644 --- a/integration-tests/tests/common.yaml +++ b/integration-tests/tests/common.yaml @@ -5,6 +5,7 @@ variables: provisioning-service: '{tavern.env_vars.PROVISIONING_HOST}.{tavern.env_vars.VERAISON_NETWORK}:{tavern.env_vars.PROVISIONING_PORT}' verification-service: '{tavern.env_vars.VERIFICATION_HOST}.{tavern.env_vars.VERAISON_NETWORK}:{tavern.env_vars.VERIFICATION_PORT}' management-service: '{tavern.env_vars.MANAGEMENT_HOST}.{tavern.env_vars.VERAISON_NETWORK}:{tavern.env_vars.MANAGEMENT_PORT}' + keycloak-service: '{tavern.env_vars.KEYCLOAK_HOST}.{tavern.env_vars.VERAISON_NETWORK}:{tavern.env_vars.KEYCLOAK_PORT}' good-nonce: QUp8F0FBs9DpodKK8xUg8NQimf6sQAfe2J1ormzZLxk= bad-nonce: Ppfdfe2JzZLOk= endorsements-content-types: @@ -29,3 +30,13 @@ variables: full: [full, ta, refval] mini: [mini, ta, refval] mini-bad: [mini, badta] + oauth2: + client-id: veraison-client + client-secret: YifmabB4cVSPPtFLAmHfq7wKaEHQn10Z + credentials: + provisioner: + username: veraison-provisioner + password: veraison + manager: + username: veraison-manager + password: veraison diff --git a/integration-tests/tests/test_enacttrust_badkey.tavern.yaml b/integration-tests/tests/test_enacttrust_badkey.tavern.yaml index f10dc0c7..5372e1cf 100644 --- a/integration-tests/tests/test_enacttrust_badkey.tavern.yaml +++ b/integration-tests/tests/test_enacttrust_badkey.tavern.yaml @@ -28,6 +28,7 @@ stages: url: http://{provisioning-service}/endorsement-provisioning/v1/submit headers: content-type: '{endorsements-content-type}' # set via hook + authorization: '{authorization}' # set via hook file_body: __generated__/endorsements/corim-{scheme}-{endorsements}.cbor response: status_code: 200 diff --git a/integration-tests/tests/test_enacttrust_badnode.tavern.yaml b/integration-tests/tests/test_enacttrust_badnode.tavern.yaml index 5d7f3892..ad09c251 100644 --- a/integration-tests/tests/test_enacttrust_badnode.tavern.yaml +++ b/integration-tests/tests/test_enacttrust_badnode.tavern.yaml @@ -33,6 +33,7 @@ stages: url: http://{provisioning-service}/endorsement-provisioning/v1/submit headers: content-type: '{endorsements-content-type}' # set via hook + authorization: '{authorization}' # set via hook file_body: __generated__/endorsements/corim-{scheme}-{endorsements}.cbor response: status_code: 200 diff --git a/integration-tests/tests/test_end_to_end.tavern.yaml b/integration-tests/tests/test_end_to_end.tavern.yaml index 6cf8241c..b183240b 100644 --- a/integration-tests/tests/test_end_to_end.tavern.yaml +++ b/integration-tests/tests/test_end_to_end.tavern.yaml @@ -40,6 +40,7 @@ stages: url: http://{provisioning-service}/endorsement-provisioning/v1/submit headers: content-type: '{endorsements-content-type}' # set via hook + authorization: '{authorization}' # set via hook file_body: __generated__/endorsements/corim-{scheme}-{endorsements}.cbor response: status_code: 200 diff --git a/integration-tests/tests/test_multinonce.tavern.yaml b/integration-tests/tests/test_multinonce.tavern.yaml index 2c97a023..34c7037d 100644 --- a/integration-tests/tests/test_multinonce.tavern.yaml +++ b/integration-tests/tests/test_multinonce.tavern.yaml @@ -33,6 +33,7 @@ stages: url: http://{provisioning-service}/endorsement-provisioning/v1/submit headers: content-type: '{endorsements-content-type}' # set via hook + authorization: '{authorization}' # set via hook file_body: __generated__/endorsements/corim-{scheme}-{endorsements}.cbor response: status_code: 200 diff --git a/integration-tests/tests/test_policy_management.tavern.yaml b/integration-tests/tests/test_policy_management.tavern.yaml index 16b388aa..3a03fd54 100644 --- a/integration-tests/tests/test_policy_management.tavern.yaml +++ b/integration-tests/tests/test_policy_management.tavern.yaml @@ -10,6 +10,7 @@ stages: url: http://{management-service}/management/v1/policy/PSA_IOT headers: accept: application/vnd.veraison.policy+json + authorization: '{authorization}' # set via hook response: status_code: 404 @@ -20,6 +21,7 @@ stages: headers: content-type: application/vnd.veraison.policy.opa accept: application/vnd.veraison.policy+json + authorization: '{authorization}' # set via hook file_body: data/policies/psa-short.rego response: status_code: 201 @@ -39,6 +41,7 @@ stages: url: http://{management-service}/management/v1/policy/PSA_IOT headers: accept: application/vnd.veraison.policy+json + authorization: '{authorization}' # set via hook response: status_code: 404 @@ -48,6 +51,7 @@ stages: url: http://{management-service}/management/v1/policy/PSA_IOT/{policy-uuid} headers: accept: application/vnd.veraison.policy+json + authorization: '{authorization}' # set via hook response: status_code: 200 verify_response_with: @@ -61,6 +65,8 @@ stages: request: method: POST url: http://{management-service}/management/v1/policy/PSA_IOT/{policy-uuid}/activate + headers: + authorization: '{authorization}' # set via hook response: status_code: 200 @@ -70,6 +76,7 @@ stages: url: http://{management-service}/management/v1/policy/PSA_IOT headers: accept: application/vnd.veraison.policy+json + authorization: '{authorization}' # set via hook response: status_code: 200 verify_response_with: @@ -85,6 +92,7 @@ stages: url: http://{management-service}/management/v1/policy/BAD headers: accept: application/vnd.veraison.policy+json + authorization: '{authorization}' # set via hook response: status_code: 400 json: @@ -98,6 +106,7 @@ stages: headers: content-type: application/vnd.veraison.policy.opa accept: application/vnd.veraison.policy+json + authorization: '{authorization}' # set via hook file_body: data/policies/psa.rego response: status_code: 201 @@ -117,6 +126,7 @@ stages: url: http://{management-service}/management/v1/policy/PSA_IOT headers: accept: application/vnd.veraison.policy+json + authorization: '{authorization}' # set via hook response: status_code: 200 verify_response_with: @@ -132,6 +142,7 @@ stages: url: http://{management-service}/management/v1/policies/PSA_IOT headers: accept: application/vnd.veraison.policies+json + authorization: '{authorization}' # set via hook response: status_code: 200 verify_response_with: @@ -143,6 +154,8 @@ stages: request: method: POST url: http://{management-service}/management/v1/policies/PSA_IOT/deactivate + headers: + authorization: '{authorization}' # set via hook response: status_code: 200 @@ -152,6 +165,7 @@ stages: url: http://{management-service}/management/v1/policies/PSA_IOT headers: accept: application/vnd.veraison.policies+json + authorization: '{authorization}' # set via hook response: status_code: 200 verify_response_with: diff --git a/integration-tests/tests/test_provisioning_empty_body.tavern.yaml b/integration-tests/tests/test_provisioning_empty_body.tavern.yaml index a1d1d9d2..b4bd1a68 100644 --- a/integration-tests/tests/test_provisioning_empty_body.tavern.yaml +++ b/integration-tests/tests/test_provisioning_empty_body.tavern.yaml @@ -10,6 +10,7 @@ stages: url: http://{provisioning-service}/endorsement-provisioning/v1/submit headers: content-type: "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1" + authorization: '{authorization}' # set via hook response: status_code: 400 json: diff --git a/integration-tests/tests/test_provisioning_unauthorized.tavern.yaml b/integration-tests/tests/test_provisioning_unauthorized.tavern.yaml new file mode 100644 index 00000000..578bd03f --- /dev/null +++ b/integration-tests/tests/test_provisioning_unauthorized.tavern.yaml @@ -0,0 +1,23 @@ +test_name: unauthorized + +includes: + - !include common.yaml + +stages: + - name: submit post request to the provisioning service with no authorization + request: + method: POST + url: http://{provisioning-service}/endorsement-provisioning/v1/submit + headers: + content-type: "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1" + response: + status_code: 401 + + - name: get active policy with no authorization + request: + method: GET + url: http://{management-service}/management/v1/policy/PSA_IOT + headers: + accept: application/vnd.veraison.policy+json + response: + status_code: 401 diff --git a/integration-tests/tests/test_verification_bad_session_attester.tavern.yaml b/integration-tests/tests/test_verification_bad_session_attester.tavern.yaml index e03a5aea..ffeb5e03 100644 --- a/integration-tests/tests/test_verification_bad_session_attester.tavern.yaml +++ b/integration-tests/tests/test_verification_bad_session_attester.tavern.yaml @@ -26,6 +26,7 @@ stages: url: http://{provisioning-service}/endorsement-provisioning/v1/submit headers: content-type: "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1" + authorization: '{authorization}' # set via hook file_body: __generated__/endorsements/corim-{scheme}-{endorsements}.cbor response: status_code: 200 diff --git a/integration-tests/utils/conftest.py b/integration-tests/utils/conftest.py index 94959604..38f39ff0 100644 --- a/integration-tests/utils/conftest.py +++ b/integration-tests/utils/conftest.py @@ -24,6 +24,7 @@ def pytest_tavern_beta_before_every_test_run(test_dict, variables): test = TavernTest(test_dict) setup(test, variables) + def setup(test, variables): clear_stores() test_id = to_identifier(test.name) diff --git a/integration-tests/utils/hooks.py b/integration-tests/utils/hooks.py index a74a48b0..0bee37e2 100644 --- a/integration-tests/utils/hooks.py +++ b/integration-tests/utils/hooks.py @@ -3,39 +3,53 @@ import os from generators import * -from util import run_command +from util import run_command, get_access_token def setup_end_to_end(test, variables): _set_content_types(test, variables) + _set_authorization(test, variables, 'provisioner') generate_endorsements(test) generate_evidence_from_test(test) -def setup_bad_session(test, veriables): +def setup_bad_session(test, variables): + _set_authorization(test, variables, 'provisioner') generate_endorsements(test) -def setup_no_nonce(test, veriables): +def setup_no_nonce(test, variables): + _set_authorization(test, variables, 'provisioner') generate_evidence_from_test(test) def setup_multi_nonce(test, variables): _set_content_types(test, variables) + _set_authorization(test, variables, 'provisioner') generate_endorsements(test) generate_evidence_from_test_no_nonce(test) def setup_enacttrust_badnode(test, variables): + _set_authorization(test, variables, 'provisioner') _set_content_types(test, variables) generate_endorsements(test) generate_evidence_from_test(test) def setup_enacttrust_badkey(test, variables): + _set_authorization(test, variables, 'provisioner') _set_content_types(test, variables) generate_endorsements(test) +def setup_policy_management(test, variables): + _set_authorization(test, variables, 'manager') + + +def setup_provisioning_fail_empty_body(test, variables): + _set_authorization(test, variables, 'provisioner') + + def _set_content_types(test, variables): scheme = test.test_vars['scheme'] profile = test.test_vars['profile'] @@ -45,4 +59,8 @@ def _set_content_types(test, variables): variables['evidence-content-type'] = ev_content_types[f'{scheme}.{profile}'] +def _set_authorization(test, variables, role): + token = get_access_token(test, role) + variables['authorization'] = f'Bearer {token}' + diff --git a/integration-tests/utils/util.py b/integration-tests/utils/util.py index 7a8f46a4..87cdd372 100644 --- a/integration-tests/utils/util.py +++ b/integration-tests/utils/util.py @@ -1,8 +1,12 @@ # Copyright 2023 Contributors to the Veraison project. # SPDX-License-Identifier: Apache-2.0 import json +import os import re import subprocess +import time + +import requests def to_identifier(text): @@ -50,3 +54,36 @@ def clear_stores(): run_command(command, f'clear {prefix} store') +def get_access_token(test, role): + kc_host = os.getenv('KEYCLOAK_HOST') + kc_port = os.getenv('KEYCLOAK_PORT') + veraison_net = os.getenv('VERAISON_NETWORK') + + # Wait for Keycloak service to come online. This takes a short while, and + # if the integration tests are run immediately after spinning up the + # deployment, it may not be there yet. + for _ in range(10): + try: + requests.get(f'http://{kc_host}.{veraison_net}:{kc_port}/') + break + except requests.ConnectionError: + time.sleep(1) + else: + raise RuntimeError('Keycloak service does not appear to be online') + + credentials = test.common_vars['credentials'][role] + data = { + 'client_id': test.common_vars['oauth2']['client-id'], + 'client_secret': test.common_vars['oauth2']['client-secret'], + 'grant_type': 'password', + 'scope': 'openid', + 'username': credentials['username'], + 'password': credentials['password'], + + } + url = f'http://{kc_host}.{veraison_net}:{kc_port}/realms/veraison/protocol/openid-connect/token' + + r = requests.post(url, data=data) + resp = r.json() + + return resp['access_token'] diff --git a/management/api/router.go b/management/api/router.go index 9338e34c..0821b443 100644 --- a/management/api/router.go +++ b/management/api/router.go @@ -3,7 +3,10 @@ package api -import "github.com/gin-gonic/gin" +import ( + "github.com/gin-gonic/gin" + "github.com/veraison/services/auth" +) var publicApiMap = map[string]string{ "createPolicy": "/management/v1/policy/:scheme", @@ -14,11 +17,12 @@ var publicApiMap = map[string]string{ "getPolicies": "/management/v1/policies/:scheme", } -func NewRouter(handler Handler) *gin.Engine { +func NewRouter(handler Handler, authorizer auth.IAuthorizer) *gin.Engine { router := gin.New() router.Use(gin.Logger()) router.Use(gin.Recovery()) + router.Use(authorizer.GetGinHandler(auth.ManagerRole)) router.POST(publicApiMap["createPolicy"], handler.CreatePolicy) router.POST(publicApiMap["activatePolicy"], handler.Activate) diff --git a/management/cmd/management-service/README.md b/management/cmd/management-service/README.md new file mode 100644 index 00000000..ebdba8c1 --- /dev/null +++ b/management/cmd/management-service/README.md @@ -0,0 +1,39 @@ +## Configuration + +`management-service` is expecting to find the following top-level entries in +configuration: + +- `management`: management service configuration. See [below](#management-service-configuration). +- `po-store`: policy store configuration. See [kvstore config](/kvstore/README.md#Configuration). +- `po-agent` (optional): policy agent configuration. See [policy config](/policy/README.md#Configuration). +- `plugin`: plugin manager configuration. See [plugin config](/vts/pluginmanager/README.md#Configuration). +- `logging` (optional): Logging configuration. See [logging config](/vts/log/README.md#Configuration). +- `auth` (optional): API authentication and authorization mechanism + configuration. If this is not specified, the `passthrough` backend will be + used (i.e. no authentication will be performed). With other backends, + authorization is based on `manager` role. See [auth + config](/auth/README.md#Configuration). + +### Management service configuration + +- `listen-addr` (optional): the address, in the form `:` the + management server will be listening on. If not specified, this defaults to + `localhost:8088`. + +### Example + +```yaml +management: + listen-addr: 0.0.0.0:8088 +po-store: + backend: sql + sql: + driver: sqlite3 + datasource: po-store.sql +po-agent: + backend: opa +plugin: + backend: go-plugin + go-plugin: + folder: ../../plugins/bin/ +``` diff --git a/management/cmd/management-service/config.yaml b/management/cmd/management-service/config.yaml index f81f29a0..a80611b3 100644 --- a/management/cmd/management-service/config.yaml +++ b/management/cmd/management-service/config.yaml @@ -13,3 +13,8 @@ po-agent: backend: opa logging: level: debug +auth: + backend: keycloak + host: keycloak-service + port: 11111 + realm: veraison diff --git a/management/cmd/management-service/main.go b/management/cmd/management-service/main.go index ba498104..78bb809f 100644 --- a/management/cmd/management-service/main.go +++ b/management/cmd/management-service/main.go @@ -4,6 +4,7 @@ package main import ( _ "github.com/mattn/go-sqlite3" + "github.com/veraison/services/auth" "github.com/veraison/services/config" "github.com/veraison/services/log" "github.com/veraison/services/management" @@ -26,7 +27,7 @@ func main() { log.Fatalf("Could not read config: %v", err) } - subs, err := config.GetSubs(v, "*management", "*logging") + subs, err := config.GetSubs(v, "*management", "*logging", "*auth") if err != nil { log.Fatalf("Could not parse config: %v", err) } @@ -53,8 +54,19 @@ func main() { } log.Infow("initializing management API service", "address", cfg.ListenAddr) + authorizer, err := auth.NewAuthorizer(subs["auth"], log.Named("auth")) + if err != nil { + log.Fatalf("could not init authorizer: %v", err) + } + defer func() { + err := authorizer.Close() + if err != nil { + log.Errorf("Could not close authorizer: %v", err) + } + }() + handler := api.NewHandler(pm, log.Named("api")) - if err := api.NewRouter(handler).Run(cfg.ListenAddr); err != nil { - log.Fatalf("Gin engine failed: %v", err) + if err := api.NewRouter(handler, authorizer).Run(cfg.ListenAddr); err != nil { + log.Errorf("Gin engine failed: %v", err) } } diff --git a/provisioning/api/handler_test.go b/provisioning/api/handler_test.go index 4b18c58b..bee17eb6 100644 --- a/provisioning/api/handler_test.go +++ b/provisioning/api/handler_test.go @@ -16,6 +16,7 @@ import ( "github.com/golang/mock/gomock" "github.com/moogar0880/problems" "github.com/stretchr/testify/assert" + "github.com/veraison/services/auth" "github.com/veraison/services/capability" "github.com/veraison/services/log" "github.com/veraison/services/proto" @@ -265,7 +266,8 @@ func TestHandler_GetWellKnownProvisioningInfo_ok(t *testing.T) { g.Request, _ = http.NewRequest(http.MethodGet, "/.well-known/veraison/provisioning", http.NoBody) g.Request.Header.Add("Accept", expectedType) - NewRouter(h).ServeHTTP(w, g.Request) + u := auth.NewPassthroughAuthorizer(log.Named("auth")) + NewRouter(h, u).ServeHTTP(w, g.Request) var body capability.WellKnownInfo _ = json.Unmarshal(w.Body.Bytes(), &body) @@ -305,7 +307,8 @@ func TestHandler_GetWellKnownProvisioningInfo_GetRegisteredMediaTypes_empty(t *t g.Request, _ = http.NewRequest(http.MethodGet, "/.well-known/veraison/provisioning", http.NoBody) g.Request.Header.Add("Accept", expectedType) - NewRouter(h).ServeHTTP(w, g.Request) + u := auth.NewPassthroughAuthorizer(log.Named("auth")) + NewRouter(h, u).ServeHTTP(w, g.Request) var body capability.WellKnownInfo _ = json.Unmarshal(w.Body.Bytes(), &body) @@ -340,7 +343,8 @@ func TestHandler_GetWellKnownProvisioningInfo_GetServiceState_fail(t *testing.T) g.Request, _ = http.NewRequest(http.MethodGet, "/.well-known/veraison/provisioning", http.NoBody) - NewRouter(h).ServeHTTP(w, g.Request) + u := auth.NewPassthroughAuthorizer(log.Named("auth")) + NewRouter(h, u).ServeHTTP(w, g.Request) var body problems.DefaultProblem _ = json.Unmarshal(w.Body.Bytes(), &body) @@ -368,7 +372,8 @@ func TestHandler_GetWellKnownProvisioningInfo_UnsupportedAccept(t *testing.T) { g.Request, _ = http.NewRequest(http.MethodGet, "/.well-known/veraison/provisioning", http.NoBody) g.Request.Header.Add("Accept", "application/unsupported+ber") - NewRouter(h).ServeHTTP(w, g.Request) + u := auth.NewPassthroughAuthorizer(log.Named("auth")) + NewRouter(h, u).ServeHTTP(w, g.Request) var body problems.DefaultProblem _ = json.Unmarshal(w.Body.Bytes(), &body) diff --git a/provisioning/api/router.go b/provisioning/api/router.go index d712581b..7789289d 100644 --- a/provisioning/api/router.go +++ b/provisioning/api/router.go @@ -4,6 +4,7 @@ package api import ( "github.com/gin-gonic/gin" + "github.com/veraison/services/auth" ) var publicApiMap = make(map[string]string) @@ -13,11 +14,12 @@ const ( getWellKnownProvisioningInfoUrl = "/.well-known/veraison/provisioning" ) -func NewRouter(handler IHandler) *gin.Engine { +func NewRouter(handler IHandler, authorizer auth.IAuthorizer) *gin.Engine { router := gin.New() router.Use(gin.Logger()) router.Use(gin.Recovery()) + router.Use(authorizer.GetGinHandler(auth.ProvisionerRole)) router.POST(provisioningSubmitUrl, handler.Submit) publicApiMap["provisioningSubmit"] = provisioningSubmitUrl diff --git a/provisioning/cmd/provisioning-service/README.md b/provisioning/cmd/provisioning-service/README.md index b67959b1..d78fac1a 100644 --- a/provisioning/cmd/provisioning-service/README.md +++ b/provisioning/cmd/provisioning-service/README.md @@ -3,9 +3,14 @@ `provisioning-services` is expecting to find the following top-level entries in configuration: -- `provisoning`: provisioning service configuration. See [below](#provisioning-service-configuration). +- `provisioning`: provisioning service configuration. See [below](#provisioning-service-configuration). - `vts` (optional): Veraison Trusted Services backend configuration. See [trustedservices config](/vts/trustedservices/README.md#Configuration). - `logging` (optional): Logging configuration. See [logging config](/vts/log/README.md#Configuration). +- `auth` (optional): API authentication and authorization mechanism + configuration. If this is not specified, the `passthrough` backend will be + used (i.e. no authentication will be performed). With other backends, + authorization is based on `provisioner` role. See [auth + config](/auth/README.md#Configuration). ### Provisioning service configuration diff --git a/provisioning/cmd/provisioning-service/main.go b/provisioning/cmd/provisioning-service/main.go index 317d098d..3331f50f 100644 --- a/provisioning/cmd/provisioning-service/main.go +++ b/provisioning/cmd/provisioning-service/main.go @@ -9,6 +9,7 @@ import ( "os/signal" "syscall" + "github.com/veraison/services/auth" "github.com/veraison/services/config" "github.com/veraison/services/log" "github.com/veraison/services/provisioning/api" @@ -37,7 +38,7 @@ func main() { ListenAddr: DefaultListenAddr, } - subs, err := config.GetSubs(v, "provisioning", "vts", "*logging") + subs, err := config.GetSubs(v, "provisioning", "vts", "*logging", "*auth") if err != nil { log.Fatal(err) } @@ -73,8 +74,19 @@ func main() { provisioner := provisioner.New(vtsClient) log.Infow("initializing provisioning API service", "address", cfg.ListenAddr) + authorizer, err := auth.NewAuthorizer(subs["auth"], log.Named("auth")) + if err != nil { + log.Fatalf("could not init authorizer: %v", err) + } + defer func() { + err := authorizer.Close() + if err != nil { + log.Errorf("Could not close authorizer: %v", err) + } + }() + apiHandler := api.NewHandler(provisioner, log.Named("api")) - go apiServer(apiHandler, cfg.ListenAddr) + go apiServer(apiHandler, authorizer, cfg.ListenAddr) sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) @@ -95,8 +107,8 @@ func terminator( done <- true } -func apiServer(apiHandler api.IHandler, listenAddr string) { - if err := api.NewRouter(apiHandler).Run(listenAddr); err != nil { +func apiServer(apiHandler api.IHandler, authorizer auth.IAuthorizer, listenAddr string) { + if err := api.NewRouter(apiHandler, authorizer).Run(listenAddr); err != nil { log.Fatalf("Gin engine failed: %v", err) } } From 96f89d77547d065fb6969b34c631dea1d9f24320 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 31 Aug 2023 08:45:08 +0100 Subject: [PATCH 3/3] deploy/docker: update deployment diagram and README - Update the deployment diagram to match current deployment. This includes diagramming the management service (was neglected when the service was added to the deployment). - Update REAME text around the diagram to match current deployment. - Add *.bkp to .gitignore. draw.io generates .bkp files when saving an updated diagram. Signed-off-by: Sergei Trofimov --- .gitignore | 1 + deployments/docker/README.md | 10 ++++++---- .../misc/veraison-docker-deployment.png | Bin 46616 -> 64396 bytes 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 1fbac418..bf033283 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.dll *.so *.dylib +*.bkp policy/cmd/polcli/polcli protogen provisioning/cmd/provisioning-service/provisioning-service diff --git a/deployments/docker/README.md b/deployments/docker/README.md index 1db29e2a..0be037b5 100644 --- a/deployments/docker/README.md +++ b/deployments/docker/README.md @@ -23,9 +23,11 @@ currently not supported). You can interact with the deployment via the `veraison` command. E.g.: $ veraison status - vts: stopped + vts: stopped provisioning: stopped verification: stopped + management: stopped + keycloak: stopped To start Veraison services run: @@ -39,9 +41,9 @@ Use the `-h` option to see the full list of available commands. ## Deployment Composition -The deployment exists on its own Docker network. Each of the three services -runs in its own container. Service logs and VTS stores reside inside docker -volumes, independent of any container. +The deployment exists on its own Docker network. Each of the services runs in +its own container. Service logs and VTS stores reside inside docker volumes, +independent of any container. ![deployment composition](./misc/veraison-docker-deployment.png) diff --git a/deployments/docker/misc/veraison-docker-deployment.png b/deployments/docker/misc/veraison-docker-deployment.png index 3b7f7a9b18a354415b11463aebe6d5c3cf82d9f9..8d7bec92b4fa2eb9a438d760b36f7cb7ee313bea 100644 GIT binary patch literal 64396 zcmeEtXIN9)wzeV&iV7-~(hArt{aNJ0n+kU|owsMrBf0qF<=(m|wG z3xZUUCS3%AbRmF-`YrZ8-gEA~XaD@ZAK!DH2a=UJ*IZ+cF~@kvJI7ktB6xGIH{gGD?#2veq)- z^YU8q5a2@wETsgJv;XUSBoSVpVuGXWoG%ZSl2_arVq$1*W_n&u2RQb?;nBdC0UCw(+!^9Z zLeivgp1|pHvQmmtN>1q~w$!K+ivw(w75C{WTvL5`!e+ z{zcr5zGw;(`sQ=z}Jaae#sTG}mw8JA)|4T^ehx)(cVfBf{~Jz4P6dGx5nc!2qHwz-O3PQWa&lMhPh#( zx(HiYh?cevoNAyI)Fx7?9(XSuD@!w?92SpZ7|Pn~z-92Z#%9{ao-hL< z)Z7?v>tn(DWORic||P~pbGs18g4}}bf?igP2Er~1iB)`#~$qK zX=3H0Eo%aCfuOt)Btu^l8!|%421mE>q}zgB^hw6{Ue?w)4=lk5OMudC-Asw@mUjBq z?&jXQroIMnLsMN_UkeHnfug(9h!_YMkZ6LnBO<-&Bsv(WB}?^2!mUAMoFWO~hPSaa z(#E^cGh$wJlTf`-Ek8=L!1`KheohAS2Q-lYq=>| zyP+{UUUWr0bE=Dln*vP%W~fLt(FLMio2o}HRMtBRLk6~c$A?FUlxf_9ruD(7N)@XewNSp4h2L*fh(Cx{31_Xw+A_HbB z4>qOS8yI3-Y{-fPx+}pAqN8g|#hNi_R0z%uLr_qlV(3^OQxwV$>jMSQQy*jpr}$WD zp%oQu(VnKhmSl{Vz5&@tNtUcd)3T-+?}U|^mORDOP1Y8sg@D1ec3hQW4S<|J8HdFh zxY?rU=422YO~V=5KrG1~cAhR^q@f4dKt@p;A?s^xW?=@L>+S1?M^bIPJ&cXrz(^Na z9S}YmMbQ=#-nk=1euWt;pr%{Zc=GtZkSZf=wo4kS%6omCg$oT5nkjRQ4A`DDIy2FXK zUNnTU7FEv;?2Dv(Fc2o5IxY~5t0B!72}sk{a=~I{?2J$hG)R|fPomhGnY&UM0F-I# zD_MA0Q9Y?%<`fbE3?k?n$TD=%NSL-4RMCrQYv5(4MDnz-vH`=j!FJjZPp~gSP9F$C zeJ^(dilVs!NY2#@$VVifop86XbtPDMfjm8ta$ZzLS*V2#189{oCxA(^6gY6GB#SXr zkoTg1K>7p*R@U1XiXx!t2t8|EMK=r$s_$ZA>Se3t=_Z4R`@(F&2p1AuUy*EPui#Gg z@-ovyLk+OHZi<#z0#(NqWTI#9Lj(fHLJn_5reJM-49x)%C=+XU7ZZYsEDl8S)w5zK zA+@Y+k#uh*Gg&2h!0)w5ByAub^q~|Vw49~8yo-w|2KedWO2)|f00$<-on9C4jy?#_ zouzW6TkT{)ZL%JgE{8StqU}VF0?E=9L4|`67+GHg$#M+sU95temY5+E$j~V}~~&BaMuKHW?+rI{MzgKb;8C*HKWUy6M|`YTJ=_ zn&s%8`b2#j7ZZKphpeZJ7SvwJ2M#BC(_Jk*F}`4gFA_%3$15Uqymn@7Y2pjQ+9<)W zmLP%+@Ee#e+E~|=Xod&-P$(`QZVW4gJ`#&CF#@BNOtdM6_MX~U21QrT)I(3kK!=F1 zb4Qy}D8@Ldyd^N!K+DSl&8u?q^JZ)Rg^Z9)a3j44Do z%tlAg(!(8YPc!lX8JfH5m>Ijv&@Ej}WW4QhmPVfLUPx0fV*to8L<6`1Mn`vN4de+B z1B$n{5yex{!^nngjfA=*6g{;m7CZie!5Gt(e84`6J5M&V;C?E#pSAqeBcWNu9uk-T8XA*Le`Qo zRMOMNYwPbQ1avS61SO)j3n>ozJR}1Ni@`k|8z*abqom{L> zHTCtPc~O1LAQoOwYY<3AThUgRCa-8Ar-1bY?@Yzeoo)@prv6S$0r4OY90F+^;8Z(* z0GHJN&By*EXTa}&&|rBz-}=ryyLO50GSbzuraR3JaX%H%8(3Y$3)-F&dt7vP{M^Ys zTDyyGh#H+ceOOnPTZa3?ohx^{gTcn)5D=w;a<8eemvtO2X?=hdK`7~^L z<(kH%|D>NtW?tUf5Ou+HXsGjvRSjjL<6Fos4j$3#|L)_uN$^v(kcp?Kua;z-{JWDh z-*bse82Im&3ET^vq;0Ka3Y zYJW2We(&A;n-4qhG8OqZ!gpl;xU7ln%RVu8_}@-%SMXh43)`??9M{(V&I|}ZfK$C& z_`kCe(58F!`diX_!Cjqyr(xB83)|Jp`hNf0Hh-E#^c#mjlvvHKXX<~W;aB+ex33Vg z{eNem!-B~Le=Ksm+Z(Q+eM6Pttf0p&7e*fr=oKju$Zl$j|ZL(*~6W7f@Mw{OerrtOn zv{W*Q`bpDVz}9@45vy>YsnA&MeH!%7s41`p+%PQ|ZU0Ya;g)*z_;J=yrM!!e>-|!k z-hJHqhZXL?SJl3a2vIYv>PCr^dVbMsztYJq6Crzp*0r!t+~A}I_7_`jS6?96hCU7Z zO-6!00onHu;rj`t|1w(L7N==PX+s&#cVe(@&2P*Z%8I&~XT26Sw>ICm-t}?-lGU+g zo#9nUYU^F|dF$Bpr5F~FxM@Xp7RuP`xOaMjteoW<`L1hiaOSDa>$1-@VsEecU>=-R zW4Wpl^&l$uW$K-g^tas6HpcGik{j0^Gi%mfn97zIEj-T}PA?*F6+d83WjA zX~@}EZVhL3F2v>B(0hPyZ70K1T&UG5?gfgu_F2;g3CSp_8ngDKfEPZ5ZHE=;+(r3S z&xY`OkhW^IHh+?Pk5IGO`hngz-G_Q#t4OsK^VHVbE-w7j{G&i<|B*+Rwf`q$3Fi>X z_p`1ISWG-_=GO{&HGo=hUwdX^T}EMm)bM5LR|86XwyNQC(zS?eW$ySF%MWVOpYZRX>*mPv?nn67giZTa#)Nm;F{v!E zJHs!eg@x0M&BASNO5IeL=-=uu9`h6;FV3}zovZP~l>waI=ZwK4WnIZ--Smi=Y`fm$rp5r>CF@FF~9JUb|f@t6AdYSDbw&0U^ z&%f(a+-qdLXDpaeHRdwEksIV2R1J17fHfxg4(Qnzd4;h&*`N0-)8rP~8ZDmRY<{`E zU7dmTRoFB~_6=@cE%ZL!N@qIdIxM4lhANr!Up0apUWzy+eO-nZevE*2{Az9XRhndM ze;^knJ|Fb^hV^AFCH7bOmP1dnZm8G%BoXY3B`}qwmDuS|spg;WlwSS1oUonzcAB+3 z>6?y_hZCq*ZII>6LdSTQB}Dkhx8=&O)zQ{3$IrydPwiWwld_yn2Ugf8kj?VOTIP}a zo0HVMLhTQfnJzeK7&>fU75Z3Q|KfKp3l`rY)}-YamEV!azdQIwz47!zgW(R7o60Yd zrDu%IS|_31I{-c48ITfKgOE%irMkP`=AvoJu~Drm#@FxVG4e?ZS1T)>evWMf1-TxrPrR zh9^~B-o;POWv?lRzm&;u*{(*h_9mz@8P&d{M%(NKztZgfX#?ZTR4L`F-bur!)oc^;f=oq5{6g%L$hkF)X{eC=hEH_>JZUpbzVgl`i44z? z^n`(u>ClLw1cL`ZwEt_rb2kd$*KYN3eP@5C;s}B709bDR>M3eVc$+jFRJ^r82o9_I zv3GLuFtc`Hv&rcPDEy_;SVZXx6RlP}`m_GBzafMgG+Q08zqxN+-068%SS_w9eMsGO z1*+x{B+0g8Ir%l=T!cftbr+DnGfRoGyv=~$jSco~)Qf?~?QIo97N-fyxc&se1^GL( z;csQO-2!>Mm@K}m<7)V6lT(#luNQbgO1ZU(bDdV_v%9P- zO&XNKd))j!p4gPzq)H~S%a@Lczl1FWS|+J`<+4)!54NAkxzxJ~TMH9%2QNwGZU+=% zs(*O3ynV&)&}5BWuMJc@-}`ncueS92>%x5ZCJy2N>zWjR(->UrX%YCn&$^par&Z5q6MiuRH$Uzj%ihqjJH+fb0UAh}SN-NP zcK`g^#^_A(N;_y>z&~8rC%dWJGhHYS?Ec|>Wp~+Rh_R7bH+XEm<+4Ov3UP*<|Aevp zVSebO{l;Qk?8>)PVmN^jdrSc1r+AFk26f3^1%NGafY z>~f7EkV2jU>FuXN+m1yKYr~Q~A>S`od*i^98~+M`X8= zoR6Hd$h6W+=%{`mT(up+nGn$_dM&ArOQ#)XZ@+s;WA)7Xg@%WJGMH}{K27}!Q=WVM z1myAkytfLskQl#B@KJMRCj;Rj{a$AsqYF8*u7fG3$cr3Aj-kf8Vl}Di+0Q@ z@?8n9^N2aiv3OW%;l`QESIh2x7$yh#LpAexZrLUyl7zEh870IMRPtTyn@@-DhHF-? zHoP<+8CLde3|WNiu0G4}5*(Hg2Je-J_I~1(I)RYUZ9*>|pEvnFFN@k-h!;N0Qw}89N8FD_8{l6XQ|W_QOXN9$e@CeM_vcxJ9d)%_RiM%lIZp8D;z7QyE@HEAKH{kspTeJIjSq_#Ieb} z46kpBq^wKQKFkO#KhSgoDx7x|DF;+{bA-kzdsid^sV>!Q*-~xuAsv_{I#^bXRVw%6x^MBXo+mc{N6qWAVuMf&B0+M1Hyhu|mx$ zBiWf#`wD>0ub-p7?&E*yke^4a9J@eOn`_~W4~2>HO9kH!J>}MQuy?ve-uDjv>%-%K z@BK2k{#MRp_ioM{!I$uLuOWG#hfuZ5?0?uN`KO&4JiBMK!h2bAQJHJ=EWbTag?N5Y zXpi~zIwVjN+2UU4zI4&Y=pXZ!(b({97%C<0F)e%h!R@f7XdJM)kJ`h*V=H}4QGrk0 zKMAV5r7$zu#CiB`v}U-|Pi#u7(bqsO)j6%aH*r_6OZ@{} z|MJhSl&Oe)N~be4MXz#ji})7|Gq$P4xDLgud$6woD5=xfDb?)f5xrKeS-v!}{P{|| z_r>cg5tti`0!r+sN2wkShfYDh-KQtst~tz;2E>BJ?NF_YhRN67+*n(yZ0~xZ_25s0 z;TH~0(R<*whJ|%k8O{7S5F4=*3w;19D*A(8v(7hI4sATs4R-30B`Di!ATHe1tm1?! z6&692rs$z36y^S>w;Gz6bq}&fvIFNX{GL7zhl`b+F#%=uO8lGIXIiB#_f$Tix|$@; zVFL23#eX#l*aWWaj`4+ev0Lh6!4c}RLxWY~mbrO)~4r}5mu zpLklf8DZ1H!{Q2K=w!e$nm&MI=Wzdt6S^Od_-?-|?apF%^t4CrJIgN}Jf`gP>EuwQ z`*-%c-=(v3oYqm{-W#T=%7tm${7_z3OBg8mhXL!V*z(;kEP3bpKZZnG z?0uvq_Rm`w!QwmuS8S|1)!CYoVQxo5rsNDbzn%S5qvNnL-3gmal2?%0?7YN*JPvHo zncrnsb$R?*LzC0=*?awENYq(q1B9O^vW`|%`&pI{*4>lqpaUZAeE`s?6FZvh`BM|G z(YI?GGgHbbNRxjAU7ai;+@^Lnb}C*=@cn;BBrT4l0FWa9FX_ej-t*rd#_tXO!Q;O& zm!bTem1)1-yY_pT{{`}xy^XaAY%Y!a$bc6y*HpQ}lq*8_9z6N-&7QW%+wef(Jscz>hh#b?ec93Hnqx`?dAK<2k(=u%GosNd>mNy2d;=e$KMY3jK zYy5idsV>Od-@#mA25b>W8K=Bfzy1%vHvBroH}aWrQ@RVt7e^`j(3_fc&h)^I#I==^ zFDIH2x3Hfc?$GsrLA)KW z6hEHA>94P8pN(8)ZsmAa{VOCpIt> zHQ@6(-7KyHSSwYYu1uH%ZI~Pf)hKrm4rB6j&K`Fgd7kULGL^|{Kk5EcH%5qqAwZw3 z22(mh1o4Z~wah~9LppU%!KLX{>4)DLER992-hLKy=2|h3`m*0Oj{`0Z`#N(nDU;mr zq!_um*8*tZajj|HbNVgo&P;FpReB+~spY_KKY&vVoOZz6-a+7+U(BNkByGV>_GD5S zfNR2>hjJ|)!&O(4p|k$(zg6n4NRz7iuDY_I@@4*>oC@cX_>ZmEG=;M5a{H3mpQ@{M zNS-fOe&m(^ko%=AY4@sY*a$Eje}N59zAb)^b^%hA6rslYOgV^_SgQ@sXGha zJvJuNz1WZt{|}vF6;-sO9lh_ZEzkbe>Dk(%-*hiFIZE0&qCr?`XZq^D-0tsPQMtn_ z{l86jZ7oCOomUyW?y-|9LAhg z-}9^P!z)c-__512pSFPjI7CpH{Ko=UtW19|Q6bv&cDB8yJ#IM=yoXO$p-lLiwte{Qz$hmv9hq0TLjE%L)dIjEETB^$fdy@T6B_l$2xo z>(>f%Ukb%4WLo}su+aoOJ^RPe2(eF9gU zOyd-ZqzfNH1lPYcKjgpr7$5}VdU54v`OgF^8kBrSnXBd)2>Y4>AO(FgLN}UBQZa3H zQ0VUNz51L_{aIGFRIQ@+S<_>?7dSF#YG&XOWqUSjvNz7ND)&%BNpBT|)%BVIJ0|fg zUCg1R(>Z8!q8b&ZS;rB4X7bSIf(OzqP2s6w?qgT0az>I9eHNl(72Pw3nA=}NG&#A9 zBQK}5*Cyu84V2N?YBJueZ>014Ts!bv%`k5SuRZ>kgWo`vA zwb<|h_)qM*(>c1+pXV z3tUR+dMbdPZTocl;Awb@u6G8H7p8aEzTg_tsbb;;=h^c=Y6X*`*vMD4+glC;r3)5? zE_%h}NvPWPph?A8iEPBHYp-}_C4Vph3P{h5g$B-kDTLKO5bOlf_R9P1(B4$OZINp{ zy7pgVXY#D`ht;0eY6cR9tNPdIBL`r{TjS1I& zflvJ)JiN>c8KV+oK1<1plCax<1fY`q`*(;s&(WK@yMn8D;(I2nhv=-8%hoR+l@Qh) zLiP)d52~$>a(61szCFyCdW_<~8-JZKTaOCYc%dD_yQ%VX$Z8-pymMN^#-Q!yJ~1HN z%ff^xz16g=9~x}rFv@4HKTT1xUpYj<3E0CWUuBczH2R|-KYmP}5HeK!bL*>#7;9+$Zdke6Z3;kdPu zEO_=>(m}J@qP@8Jc*d}D3W($I@n=6;l#1Pl-d;NNtTOUqwW=NXVFl#vSyQ3lATB5W z2?&VzxslDs#BaB&v8p}{dh_Y>h>A{qaL~_m_?g#MTHi9mYOne9b@wFl<}QxMHCyxK z$$_VR&P;>Hh&2q4oO;Bssc8jZ zqr${ZaHO=e`ecOco;IM?D33+$%w%tp{3nJ@E!!VGZxcAmj;62!nl;Lb`Y@2mvx$vyfzE1|mlWsmPfy;1B|M*{;A zk2)*yD7N1x|0Db$(5dzeM z&!#oK)Qm{ksm>BN*Ru!b{hp^Cu9SWTRYvpoayj+^h%X8p*F0BqQ0!Blc*Z)b zaaM3?6Z&l9SM=SDFAJo5SNANTMjjQJ-@AJ1eZ0)NKo1AU=*~`H{+FyTCrLouzU0RJ z==>BQRx?feNSCc%-2MD{|7GVI>-T3bKT1q|E~HX|Z;q0*FIwZ9WOSZJNOm&2H>N32 zlN8rT9rz0h1fFGWhc=G3G`=i|J{zknn^CQTD(%T}Or2;B>WwN6a@&06ET#k4?m`zM zQJno|-5^oWob%8_QsA>6ugve&o&Bk#w|YtVjT->ifYrDqL=SKW?>XG){IZ~_>t(9T z`9n65?(m|(=UbX<R=6tzO!9gQ>vt>l{!$zGzsXCECZq^&s-o8IXInd-3#T#|p};JMY*!XA6Ah z>?IwS;(8>VlIP+SZ@G3Yp2Qd%-*4*1r|gf+%Ced;UWDwcw!c!a@pHny+&^>cmv@xU z!Z2Y-^&{VmcI>Bp5hD90n677z7N}{MDt*bgrg3<+UHh$^afwqF5Z<*iSFjRtEJP>icmHkHjye_Zobscolk-B7=bDXQd z?c0{eJ1KMjN?cYy5>yWByJ33)zn>X){X8J4NuhRwLZXNtEXC|F|B+_$BZtCb6jmN?qd-<>JKFxaP1r%6*r0{BTaC{=bxa@4bTdFv~ z&F@=c+idn(dvsY*XL={^tmWKyCtW}VilespG~DXrJs-fEeL}lzjeovW>UrV#{X^401Mx*M;2L&1G1Zz9EwXtkUZ;ZS|)w=V98y zinm1$11NKNpF-zrShB@O9qU5qp6*=4NZUTS>|4Ao+l$<}zi^2qxz5o)$QNgBoxdWI z_3jkNGGyu4V-RN>KBle-er2UO-p+n|J^y*qN%rA2{=Gzwaf7egZ?AKly9Ns^FyHs- z?JHMt%fSrWBg*MWpmd3D-qW%?dtBTR;5CYF?~5Ym4w1F7*hFNB+wdQ`jvFxT_PCZO zk@+Lm#WP@w!W6wHkOOUmh`RLFxZ$lJ`nvvd9!i0j|byas32H<*|bE8iQ@oK0LRy0Hd5pd4J;Y-7|H!lhO|J1-}fq zCKmp!@c#GCDAltXw#|$Dq8i_x8z+_UJQ69S<5E6e zJ$te4wg%~pdE+fY->0(rv*Clr9c>S;OEPo=>GzZgL;D|z5YyZQ^>#0b?YKXY%i|+o zDJxBg0}0d-{$WyTKk$6nfzfm~x#-4y1*73t8;{B%9TXLag^gmRR%eV)-*hrIeF$Suf9v{Q$Up`&r_z0j{yaWTV4<9f%K=Z|e0M3-v;84}G z`@Ws|Q~08-(6Uir!CwBmlA3`9%KQZy8h2$m6!!tHaw)duJP8;kCb)FV^h?yiPzQ;n z#$189DxNQo4aV<8zXVA4F7~L+eIHAJ38b8$GG}doW%g2W?mxr77g%QG-6JSn(c8OD z2S(|u{w4Q*!{ronho!q4*CZQs0A24~4(LCt8TjRh5%Vw~ptG2;;_^MsSAfnJO%hpW zzup7}i||jmi2UVZhBxSc3O7)-;SP8#lCzuBh4+z2&LPg(c0QMf*UbR_OSpf5d@p|2 z>8-{P)iXy+&^lwsAN^$#H4LO>d;rOYgZR6co~(g8vVH|tQ+_Nb|wUu+%IYA7Y7Eul?t->$%xzTDit z6Ctt{$Lv9^uGotP1v+}avAgV;_2Prt_U$zaM7^EyXtVml-5H6fk6T;n=Myy_6dBEo-QsXw%UIm^lLL0!lKo9J-^Xb(b7Dl8Q$IuA*);fXT0X%dMfRl zRkB$=dh-|b6{TwWOF}ncLvd1EW%+G2p zy*jn+XWEeJ@Y0I?*}FPAXC)nU^lDEywjiI~WVSXSk#9_RWu@ z!mVf051yOr+0Zjt{u~D@zM|&HH%!S)_g_v6^XxTt4g{NH}V2T zf~|vNxM#-A&-=e;)SG}7BWbVkh>BC)kd}{?{_G{(T+$3^bb|I8*~z3!Jh-)d@=Ca= z7W;9;J9zf$sPf!6(Z1XTt^MIi@00geG_v5A^R?8>J2sMnV8u-n@Ul-@Ya{pt{F`rS z(-cz^xRkR#{BmUHAiMT^@lTD8sf2J#mOeEew^8DemgqMZkl;TU0by#cf=Y2Ln-vY# zIU$?e$3xFuptiyuo`jQ4s zLWD*k1CngG_Kzd_tj4*fW!7jeW=c))ZKCnw? z^EcQ_6H09fDzy5R&{?R)W|i(-xT-c{c=fI#p>LWpH1j%4t|9IlgDKQ}tU1zVd;Vgk z^*tAR)}(J}&=ld~NK>R#THp}WViBk+1vDH|+g{^dLw{VVl8W0DHVB-J7SGnosa?S& z2uj-76p>{uo*fJWay$Evg6!cn1) zzXv|PP_@*iPLuxNj;Pd!Q`Md)zz)bmsDUGS_CK1fGQN9_d_N3*PWY@MlIa`yW$fcd ze_?qxqwB6##mbv|YxsBi2j_at9t5sCI{SkW;80Zql&~MP8IkL~|2QZc-Xqpt6>Dha z++Iu*E_Um?DtNF~6R4Kq9tc8fiHw;$d|iM=K80&+GX-+n zRN1zUe!bgMUs7YIo{oimp5s%S6VPY{8$#Vi>`}r2HYz^zU0g7As<+-`#6mou!^ZPqMX}_=D{Bw9-PfY!SEj&4uEY6vRu|H<7wBE(*9F1_*`rRN z(%O~6Z#H?RmmJkzC$ZFj%+0d5%lTbM3pZ*U6I@8ED#u&(YLE7pe0B`qHpg)-&D=hG zbLD%{lZ0;t<=<(|dyc+#s01)`GvUFL^bsJ5#Eew<`hKo#3kt$dPp)Bl!sP_z^D2IL zO!}X{f2{lE1MaCe`=p!whZV|cgO;rcKclOiEdzn1GdO9G6V=Dt%pEPx%z1OsnGVy~ zu8?S`4q?Zmqz=XM#xv2YS7^?D5qV{s$Vm<;RB-&&h_{E7e`5cl+vLC*X291NqXqZw zSgNdai>s@v`1&IAhTp_~^zx_oM(h5S&Eqx$J!bK|DyPAOjxxbv%J6B_ocC%?fwhce zYx%yfzyimY$F$$SSc0Fs+V#=@oOKSbN%>s;Ays3>3^Hi_>GDGg2>0o%X98J(gJ&Fg zDTS2yMTsZ)J%{KfJeb?#Y1@s)sR^3T!leu)(Iu4gJ%EzwVt0#+RB`>)0wWUsvA{{F5Vt3(5p zcv!Wwf1ne~?Vrz{saU#E1X>%u_!>PkQc*B+^0G6NAG^n8|G~oj_L!pW;pP05`3C1q zLiUBW1UvAc3N*WSGc96r)n|Vfrt`@_`jsB$2ag}&xIU!@m8eu832y~U7A32?HSU@l zP8jw7QoWGK4f1|l6Ib`%HRNZ}v&yK9fQ>|QGTWktI-}T{!14_4Ygb)Tuh^sVtu0%c zvj1bVOs zfu2FT!E(ndLz_X{KE=kiLYrvWVD9thh25+D@&Xg z*?!C#X}$10__V?I8~BB$P89zk)9$2HdDi!l8zeCyip86pwr~Q?|LcN{;>}P*;InT% z6O$_)KRQrCM}Yt+9LW2W1|C_+3DKwGF`Ea42WPA|Yg~$l*T?Z0x#hfN1NY{rotx&k zAbi{4Jx{4RRn~N6+t4a=-iX!YWYL@(RP>=ieep(A-miC%wyRxL?BIQu?SCb23q+ZP zq4V%Cae10+EvrKi>b>yo^gKCC(lY1nd=2qgUfJ9z@FX-S8`0(+&(tR747!a}Oa;Y< z5j(z=*R3^n<~Io(Z_~iH%fFE44fHhI)J`=x&?E=udIV%$6+x@kkOXAveCyh}*IdDF zU-lfXO>cr;)yU_W1<7TF)Ji=#St{vJ3gPC;j?>=O#D2z^v)r0yi2*LV57EI!>ecTPEz2Ezxw^+(qHV-5s-@opQF!= zj+~w|tF#pUWUV4-# z9w`OqWBk*PoWHMfaj+)9&nB2Lc+@=e3-D4t~K9trLA*YZ9TTAT>a&>Sk~x=D$|HBgO?zy9|hyzlAv*Tmez+sE}&#RFL19# z=nCcuWebFgS9v|%n-xLDrd*PV$zH|CSJmF=aST_>I2M!>dC6u)z50p%5bKtFI>3s$ zfYR;C&>&YivBHHwBwdj(K(aj53kYiy+y~XM$#T3+T)f9A_g~HQ<|Ag94ZmJ7m-;{M zJf;`(3jOL3c&vYYWg%#wC>un(7bR}__L392rbVcwGacHSU|uusAUr{ZGSu=WGaDd+ zuIj&*PluLc+GVd|{$$7R~zS;~p18a@l_Qc!d zcf6b-nkwn3@;2+NCqz&is$;&qD}C4%`ZE-9(;j4YtD-{8Rf)K&48s-wN- zsU;&PD>h2*{H$cQB)nmI`rZ+XYf@zb*^%u#8k)IXKBiQplM)Cn!DtCHI;WvG*x z2~N!F_VK&rr`v#*p9ao>`ES(BH{1DA)qDpgDwu5zU9YtW37;3mGd3MW0_Xkrh-P^E zxOFOEh|gC05?aQ*h?PtJp;c_H1d7M$wDcFsG<)pvD2*-mC@H1N8Ff8X7^TYBEof5e zM@w{h8LFmud1x@{+bbpzvN@1Vg5<(rWITDWCE+o~(K&ENr|sULK-3*R)lT-{(->@w zm;y~AuEE<{+@XKt`R6lf#%}$BBao3BA2SO!`bx8i!&tb<2cxuJF%8?*kE zav8iNrnU(N`DGT>;#kMju4KfBH2RPCG{^Ib2YHSNv)(?04ExU%HC$?#&f8v3>Y4m; zvl+APIv*dnG8Dct6rc2(F_SpLB@Ta8Hp6Selm+!(?Q$0LWYn<1~Zp$o7`5dVaon+pDGxudW+|h|uPS(DI%c=)VV2Yir4+6LCS{@OGK@q#Uc~@oklB zP%I0t{=;?4zM63e!_@Z=;*x}Yk!#OXaPHP%Jw+vGM>Asb&*a;!M<17Pdc2-~zIZG1-!KZ-!}PTQS%gcMa;)k zOt&$kB_7X|Hf4+9t;~^Rxk4eJ`rLK73I6kzu&{)}!ezk7qw14Mhe1^=vHe z*LO7c~!=JLUsSU!9xGBw3{H0F5Ah4WJVr&%IbWA%6QAj z!}DuTuhVKiZt<1;$} zHvWvkCnE@NP334Ub<{sFHT7^M11M1hwO2E*3aAz8oStMRoUhIQ+?ii9>RMvMUF67zuA*m7mUD zMZ#CU6y-xYS^^th^;M1AeCl^bvg7(RDuyZOky~hK2|?2<{N=4dt17$q@}2yunpHPg z=;6dDKPoCcqh8vz+V6Y^H83?_NNZByos@A3Wie=TaB8Yr= zjZB;f>zFxfuv;3_(pups&my(~>B;xQs~2%nLf<^xT7-0&@t7yTuBp6Y^z)mxATUiM zPQ|KKUy^la*HPruPspnvIBVx2kx{+LY~$|Bmz}Dfm3R!NG)q-miI}Z_S;1B3`Sl7d zWFV`S-uVlSY~1}aw(U1mex;`>`dKLJyI<1aWaPa0#Sd@KT+U|<(d>zQiv`#7@@ zG_6`Im+?pARWh^f8tdg9u*M*{)V`K@5!XFUZ3}5=Q3)f@Y-{dsX7)Axyb5k9zvd$B zaIGekti5gnQAhP%ecytMcMjB$#t_G?ZGCFEtSfJ`t`5x1`wqsiYTIiPQcg>q{HAL4 zemtM;c8hTYHkbX3@xD8dGOUXR4 zed%k(C5bpyneZf;q=lA{yqVj4UQ$qsdU{%*UBw{#j0S66fEj^i+6_NRH>KT&$1H&S z@{4Hc0nhWXo8nJq#M-Ck2FpFwx9cxUEJ*&7Edt?jx;J}jV{dcXV(lc-qMYVV%6NF4Ww5s;8~OjVKI90+#TWVV3T3aeOA*5ciXt z^-GN$JTfRr5Vmx!32<>zp{1$#<+en#!7Ont7SBmms8y+8RLkOpQr=^izxy9jNa{-u zR2LF z<*L@{1Jk2l_Z^(ZW_6xV?`<~wS<|gtx?M}|XpZ}3PHTE7Aehl#JFR83_pQeNIGx{P z=6Bbq$sl!~dA#n#(F%T}rjMew$!smOn$ADKHx2F6+KMMkVZuR(GUdl9BR{QRKIeP% z6^~XGi{Bc}y9Dq2!eRn*P~DS>=3b+eCMq}eX|{m#M8w`PuW5^I*v|Jf`~3Lozy>4{ zW{EO-P#ep!Lw(oV$pLBNVO#rVAWJy7oTR~E2{Xe=xy==Sav}CQ%B86&coRPITc%Q& zeSPVe-nuHaBHU;JAIWl%%76>@zv&C|8Jl@#3!4xrugZj(cU(;kzXVqbM}4gH!J~Glh4gp2Y>TWqozrCZJtQD#ZR@Qz~Q85hM+G&nb2Nx7o>#LWT2kc{f z?*2%F94yx_YjRAvr1n3WG0!FWOg`4~JGf!MXLfr!ma%>7G(^%cC^1%VeKyg-whdoB z>gZ-dHx#+;lAWJ6Dy{WQ%J4Ko?y5ZVE}l({MS5*xTEuB#%Nh5V5d%jw;~dmXXpr;q z*nY~KFA`x5qgvl^GXbQesEYzP1vw zF;i$Xm%V(XwlW8-(}Q=iKdXiu{mKRfg5pS>hkaq)&EZC9gpOR*BOfqMNE5Vtu)R!A zpMW7v`18~@ePqDCG`D{?$>tEzVsB*=QNJ8+kResRU|t}mn8VC%fOvE(4kZSG^`&cwxrKsE`5orZ7biIT0m)*W{2@Ho;-ec)U} zCGu)a6O-(pDPv}jf0=|m4oraSbWfK{pAYo_Zmlmtj+micR4t5W?N3C%0}GoKe#L`* zKiA1^j6*cBX&!RO7v&S|`hq=8x4?sg)buRhySQfvHj6)K@IE~gXUmaryo+8A=X4tX zm?t!SjOumQ@@6l7#*TZo_2iDxPJgY@iFvE;@>aQa=jWk1@bTa~H@7oFQek!t8I>Cu ztUHt%%g!0VVZO;Ad7rt{X>{y4_bxZ}vakocPGXxeR;GcQxZzIFn1F~5YK(4A4rPZ+ z$?^Z(TD^lCrtjj96?X_WF9wez0*x#@=0wpqEzt;U3}IB%@?idI@}X}x(F}+7dxg?j z=K7&EKXY^$m)~GHKhti1{+uo>4x}5S;El;4JE&ItKspHo!u4R<8P+$jAyx!Ew&+8I zt&MNo?~5Hza~RL)G)^@tr}at;o9}Zt$UsE+SwRT&G9+b z-}~nE&oR^LpN+MP}mnvlq$d{DdxCxISYgg8bE^aArv}+0S*K zq2EoR&sHIPP)=l0F0wDkF51Y8%4>QkwNo9xI&Ps8?F|A|VPV0wdvHaw?dQR6M}anH zyn6FnqS|miINuAud+31N2k}aCFP=COw<2Ap7J^>ku`e0AjgoLUS4?&gx8IlsPdxNd)CC30NMH}}??7i9UT|4!z#7v8Trbl^ z0(8lzhY_Bp97^~5B1BCU-{l4ayhgdzOG8c3RsTiCv~M*4eBe)iQ?cdw@LeYWy&2;i z2toi??I6o#O*5*Or{%_0>1~1x&po+Zdpi@Q&9i!nY&fT68!*rYus5|AecAvx086Sa zIuX;)(dlFd^mT6~tWIf?@D!O*#?ai;C=PhdoMS+7wXOVoab3bWB0x`cO%*zfx4q0( zb*ttVgeT=?F_CY7k7RVs2QE1t3jORu17OF&yxqWOm7A-;ZtN91M~6ca*%`62Z;qR` zFOLC`p~iR3M6x<}CC<#{d@lfgtf&BaEl*|o3L&je-{58X0Z}^iN{+i+c2{Qx@=JQmH;olYkbtkIt z7HJArQ@|lO6hWdD-D6^P=xfu8Mofnq+*F*=8y+YGV5%z-_HkD~-B!HbpjT_(&&i_7 zb3-u{*hc@9@(y@(v+*n$>PZn9e;`zpcp(&zztEq(i!kgv z?r0c2L~N0^svhJsI!%0v8#&L2cO7_f)iv}6pz_$7kujB7F~i(EvLv`sKRU(yvdn_0gUHugh{o>bGZfJ(Y4HdH=RjnxO3TWl|?n{5< z{e8sN?4_t94}duQFY*llORaORv|nSx$b>)3xne~gpn5a)^nqEJ9@&+gKJbX-`bYa$ zj{pYwbD@o^T*zCr-a~&VaRFwaK7^siJ%SryvCOcvMZt+`acDfaR0+j3@-WvsO3{? zTpYTNDswMk9)0}z3n15Ox*9!&ZeiS1m>zA z9dF`H_%6qOQ)+)CWY7SEvWGvL4^YP^_Mv;1Pky~J?E8&7=&`>9s|svAlmN>W1PB?F zSQsS<7(BisyHcz9gRs>2=TX{jJINRT%yAqDx3}_hGeJR_@rdyw#BB$3M&~PbVkY!eVC!%cwl06#I+5<_P0hnZ$}E9Spz6UXvlw+9e5pdI>Td6MrUHiM8CA>!@U+m zS9Xzw!^fW*ly`Rfa+rjNa(ZFAq?i-UI={-mhByvIfL0ZT1Au4%z=ayxUYMF8|Ma5U-L%0k zwBCK{a>{8ak+$4%>P*5G+T6DdjK*&pJS}pWY)l<0k*5G|Gf*RTy`P^QxKB0j#WrdQ z&SB>^zW_w6-e(DbuEm~0wz{W0vh4#)s&9=t-Mw-$XG1@ouGRvnSb{%N{j^{fg-|7y z=&H6qW-GrvUh36p_t8+*hT3`Sy-B@o$z9id4QIxat0}Tc5)O-hVn1_{_Ui3Cz41%> zU%#n5McQyD@tNqI@t1pK!{it;SPyVFuEwpFWrl+2J)kJpO1#x)@9E^m7-@Q-Yrq1s zL2uYa753|xzCQ`)hSD7XI=6+QsVES|>AwY0=$?vn+doRI6r8(NDr$|aG;ayIcI~#g znq5z_KEQA#CL2CE*e>L8-O7}10^*Nlsu~QhcbUwSEIS#hubBSqIbPEUbyYlcA)UHz zikaYAvqkcIF=AQ*&Eh z3C)dT`isKwm$3luF==iUEb+bBzbR{IE9yldZlV&OB|F3Z(+qA~5!k?R zQ}Gs{&Uu?8vPcc6O*)a2Uja5qww$!-GF3bQ60(2$1qq~8lWR1$p4?mw3#HP#g?|b} z?RW)buE4p9tq_P?Ui?g^=62xh;VekZi+5-@YE+U~g!Nfi&5-mT!ERm3OLC_u55({- zi~zhglOZ}Q9#mNpKl1)a?R+3gy*qZ1h{J7c=Bq=V`7IIdDp-?`rzlCtIRVz?0%nqZ*kH{RVBf?!won1fBxA zMAw;5x(%4SZ{W!cY}zR3%dhg8F-N~bt`!$8s%Bb4SCfx^k^d%x-&DMGSu`dbP#1Ak z_EvV&r98Q#e3J=CBZ72PSNOiL8c|fA$QUeJkfnv$ZJBBZUHFqPQ~ct7Wr^*$Nl0wZ z!XeI+BbX*J%+WNLxsC_(}6D2eWKl7AR}M!lRiw8{@J{d ze&lgZMdDN+bM`XL8!~tEchSmwa56nTz08-)5Fy^XXO~_6d8ih(#(`9lTWw#QH`?nb zxT{&Gr71%q9^{tz6Z7WiZL6o{fD&v`rT+xnMR%e_T8)E7fFh{ps1H1UyupW%K~7sx z%LQ6E*OvTZ&5p?#sPZxZ7Oi)iE&UcoD;u^{+@Upwfb5hnJyiMr$6Mcd(O~`Oa`s|M z4J+~hMX5mW@zG-)9V1iI^i!4YK<75_zwJprIed=pD;$a~{_SOw2lOJ7l-QIbX;H=2 z7#_u=czgqBkN-GexlU@EtICTl{7dVpzg`6VHAJei?O6t#44MAB1PJOYA{*T15uvdu=kq)k}Y72ky5ViN!2pUX5=uC7#qe431C0ap>AXogoth(Rp!+ zYwV+^a&+KN{%FKX=#v2$Uyd?wsV8_@-ZK*PHJDQEpg$$OoZQo z&9t>$$&kk&qs*C9OPT4o$P>3=fM9oU;PDCpiR^hnAUxxA^j!E() zmP7GKYByhX`#W7bCGORcw1;wQpmomAhUasKT#DOnopCg_z-(bJbo0Fu`CQ|bw+M8} zr-sgK<0*#T#r|7C4#2}G+PCrfi+xk@-*X$(jGB3TfVmV14D?nU^MTMhQgLI}Z1_s3 z@u_uL|FJHWjlQuqyKm-Zcm84+W8g*t@8u(j1Wj?At-66lmHi5JVPEBxbdz~zFKom z^Bcq-1^oqn6cx|JwUOX}+v{uVsz^N9{6xTv;n?5BVe+;Pzt17A3DN&4GjWsbbVx;L zCZ@qUf}8K`w(YMVC2F@`fh24aTbQ5a$OSey@H}!WJaP|5oD{(bYEegwJ{Zv6R>kvo zK%NKc?~-2z@{RxfFLUgHHjX-i2~r8aveJA&N2&Dri|sUri<5A92{A8$rC?bAkKKi< z+AKDFJUspcaaYm!H>N{?i0h`M*xQ?6&J>nlI}(HX_eE%IoBcW`bq?OmK1 z|H!eh(J|989r$X$S{g5oBSyS873AmsPj zMzW20+lnZ$oWSK9^?B{dIIjS-mK!anEnwO z_^H7`T(4`w7^AZ}c;Tp)JT3;~J z`E^cSA1xHmg?_j>{R$y3z{wF(t$9)D3nhi1`5HI*-oY~+;hltDynQ?(yCm?$`%%nD zFa5?S*nL_;riOb-;nLn#TC-amTN95HhJ9nxg#H|Pl${e91^Z>|`NtyXJ`K_ZdZp!< z7fB^->}t*)=16!YTE#hlEg588?n-P;Bgc&o*D?O{sVjUnYHrfWH`P%!t7kx|Lr{PHcy@``m1Xm3$mu}RaNXA+y%Oi*-K|?3ZA(o zj19UZ64$gwM#iR`U#)F6H)Jsr_mi&@l|Yf}XQ8>V6yzwnX#?&)X)E;p7;|%PizkVqZ-Y92L-1zts(L+W2aI>I<&MaV#rI#|L zGWlySN)5B_#8R{WWDQ-FgD)vf)bmO4r&7%Tk*gr*Ze6|O~Bo15tKXjI6k5$Q^k;(DX*bcoZ@ym+J(z2s>Xl`tu>Zu`?r zqQFyI!(NHo^|XVD57sF+wQe*40LiA0<|9XycTvU;VkR!XLu)p?@(ZG7yGzieILhaa zj&Xw>FyT>THvfWE4o`(vchL9S-OUYlQ3@{V^hP&B6h5AzxypMvULlbJ&HwRhvQl!D zzRODEb2j3taMLeKbOz8rY*_US#3M{lCLlM8t?u)M8{%!g8*OK_t$W5w#J6@tmdY9& zdRo@HgD*~=*}4@?{$i0+dA@F#^{hI&&UEr+!3>O6hkLLS# zR+{l5mgzjrto^%OCCv>tcmcDim^)AnQTvdG{-oNjq|1^)^Yi7(Qq}lnjb!=f#cn-W zkVJf-GwUf~?G13SUu=G5*K{p8A(WDODkejkhM7XbVIZ+OCo0LaN^7ufV5uOA{Ke~= z0S|6E{$s6c6GBbPN-Wjieu_Iy>dJrZcBbU{b>NWGyRU;xu9{0<%X|R5A5HcN{+UKZ zqYcoJx<4H$r>oTec$9s)Ys%;GOjBNLW7}Jt+D%93#EAB+F?(J2WtO_1;u}xv`Yk!vZ?)0-5wO{p+ZT9}LKv497)k?f;ZqQ1w!~?Ns+UBD|27Lm$%%=@B{^MR) z1VrVg0Gi)(Ia1;05NG}AZ+%Y=DDBwxGP{suU}Tj9TCeSA^i(;wO1_M1Jw!%9eIw9` z@uFvk;?VqqV?W|Y;!YV3%iY}bCIL5QD_ZP}w%e^q+`mw%4U4_#;>V;5kd+g7WuRHW z>i6WUp7m5i;X?dBAKZG+3w?gvsC}+FA|cJ&5P0@Jc4*rN6&E9__>pj--La^p^k)X* zPFd~I)qRu{!ZaVNj}EX_y_ww?h0`?(E}}cg?mfPxbzds|b&NT1VZZmL(wwWT9G)py zy%>LgCA%BWCAwBtwfG9%(UVBGBJDipGdq3h0(2stb0GONkX6jzQf(Ii9`K<2hwVyc z81||e#lJm_9}0KHl(6&y!TZFJIJccy;gZ4slk?o_q&NuiyF^p-hiU?+Y{LZ)AZ)ob ze!VtfP`>k zD(;j3^2+og5M=)wZ664|>6yZoa~BqxcTMv|HddbdTwmXnipC`B$SXKZ)&cJKB+QvY zef5v~xi<-gcQs?n1@wNV^K72c$kke`Lt7aVT-I>X<< z`I{O3X3_s?ns6`MtVyVfqpeglvq$xNSiV%8vE_Aia!M>dA@~)$rX(l#+$*aTsX`vs z>>x*<4?%8Big!mSBwN}fvNccZEFCFkJIdA>dn!&7ZXKXU30?bDtx>zp(qGM78?yuz z{LVG)%$r3Z%ZsmXu3;DYipz}g^AWa$DfOC5d2`r>sKy0p!rT^_iF@6}rTSnDU-*WX zZ3DVA##w6w$LhH}CB_JSk-}C*8wyN;9KC(ja4K``cVD*CxzDD9n{>a>Bh-85xVWob zDwDOBYSnMvYwev8gh-r^j7{Y7RphCr{hTUY@qP44{3e;Qcw2x5rMl(f^zwL0<3&|> zPLWm;hMlgn?w?gxjZNmRbTy}*sow1h*9w)4(oHh~@AVZmGqa4yEk-A0bXj0dqXv&Q zJH~OEi_yx|cg~BhY0KT{9!@s;EEF9l#9)Qj)zcfXvnVxrYB6rKj2f)YO7}7MH)+cB z#;bU3fwWWZzC%4q9Ek8SH__!UEVL7E{h%Ng%~Rs(oXhzVShPlfSPfpOqwbFdBKlp< z2jHX!G_)J_Dv_iHanByAweEe{N=bF3`$S)@@de`QIfSjHv-d(3Ts|c!skceh>J}Q~ z9nFS39DZjR?A~;Iw`TGEyv5v?4JUSaC=|lt6Lf>ZEhYmu0J zZ*Pz7dS8iGr?OPE>PT#O)o9E?~mx4)riN2nkq4+~TZl`iNYkDhG# z?K3*Mx+Z<21}-CgYEELpyS%=q0JSUiSY+oJnsz>z6_hloBO7`GYDo678YYfuWBpm& z5P7eRAbHn#m!>&X&DbY;JxfatW$H@|P`dqI#ma=W+M;)rvJRw0wAWhnN^e#0v6&2E zYV>rgFA)xMudVuAU)Mb)P{jp3#3zXG#3W&lVLrphuF?mW7vM22EKMV-J~>FQuY)3o z+FNx+Tfxhg!xk~D<<)Vk^3kzKKdY_4Z8v}vNXBI21A#)F;dU7p8xm?yJkkZlY}F?n z;FN{#ImjaRljHrY?cYowwa$#RSJZ+$)TC9KH7kiPGnxmujFY%dtDvs2Lv0Mk0A*<> zFF_J%C7N%+HhjU0mI2=kyiOzt-KJ&bT*_xJC8f*-S+WQI=3rt&7O&=9w#x~474AqnIThXkx#66x%Ip{_cv@@((6a;r38qgzeX~sDGy)8!bgzv z4R1M_gzM>AavZXds&48#XvgVvLIoo)8fzXCe`wX)F|dRyG(Sp26Nrrgt*iUJ{R4~g z4YPygCi?b4LMK7%IIq=8MORF9xpQZ1{e(s7ZDJQ7R&BoZEV4|xgNn9E!h%?HIV+FOmOna`9*t#fs+FIr+R_YZ{k21-vTiuH*#DlGD@iT(_^d0*g)4@RxH&-K{x?#pQJ)1lz9MXI_|u zV2QKSddlG%8qSgC5JJ+((8Ns*5t&kb37h>-aVjE};cE*2N+qA zbg$b*O*YG&ae-_C`9`K@JX)m3$9@EZWzJGK9r~Z+5O2FopA<+Y+O>9g4>)ZrT&y}l z4!3N40o@R0;E7E@&p}lNh}{QXOPj56RYjWbEAH-p5R|ex(=?BCw%hS(h~JbV#hJsf z?H#G6;IKN|6d}TU%v@0DBzZs&hXV`c<}Tp`?PeN%9hvFUyvOLZX<_s>@$CljmHbjg z?3}32ZtwTOq+YkCCq;d!f*IwACYKhJz1M*dQyR>7!o{I!|ILo=-i%4j;2w3)46$h& z=WxPNCfx95HHMgR;)7tTC5S9W(^&%15b(8cz6C5C*Lt*IYU1+a$)sNeF5^b8b5)I7 zF)8-Oi8`BkNe2IxDlovC{V%r@NSly^y{5EtmtQuy{pnNGx(-qQqvl@7+) zmo_J0b@KFYT>`ikK5S)uXo(D|e010tMX*Pd> z>-8EER_*GWl!&T^{uZfx&(tC3ux=1NzY~Kr)^6Qq8krd5gS#An)9!kgwumm93=dii zBK8vH_Gc8MJ)r}-Og&-23Y~cGk*^M2H|S<qN14tkj35B)V6I6F6wGO73wTX`*Yg+P=Jz%|lVF4(7jYsFqK@+o(YcRJ_-ooFhs zp1d2bnQLoVedFEV+<@DoP9wy&%5NLhBCn#-V{ojcp<6AA6UdZ~cgi5o=R$91H>m^D zS*Hndu;K&woZR7fbxO}9I+L@GMC?UQg9J&=#0<$tV1S?`vT%+MInaBSicIj| z#c1|Jm*Dqf_zFoW1s{3;l?186GUtmou-X|op4yeZ#A*fwSE=OPH*aCe2Z(C-UIr2Q zk+nJ%Z|{U7;z~-lKboYszC3VuXAWA1=+_zxmh*4eXgsK_IbLT?YsCf-#{^>4Rl$uD zesx(kZ1<2b>&=@ozjLj6D(Ggc-IVrFl+ZTbtlf;H6^lcSNgG5(d1XGl8HRIWxL=)) zTepW>woW4(+6GMtmh-?&nool=HdOL&`ogmFwag3sy`lU4edVPT)l6O{^OIolTzhLl za8qebZ03t0k^c5LVNL4Ipw}O(a~-a&Um~BTRha&!aa&l3V{)KVN?DNZ`V)yC-6-Bk?qJddZ%BE@J3<#FVP1-BzK+}KwhtJRo=Ketx`X2 z%e(Vc8v^yU`$h6txUuw&bs+A|n**=8>XfcSFh-II+Z$`HzBfI86a4-~A&?JLRs0K^ z-riel=$uFf4@>_i5jbYDpqIE-XuNufB*G_U6=`)`ZCx5s*8)9YWLtJ6DD=3XGzR_GuW!>~8o+`Q)8t%w73#}kF!p%V#sk`JLW z-RxfFH!)3KHos<|kOqT28aY=Wv$UO)cHBG?feLWA&d1CIgh2S@_|cbILK-XSMl6Bv z`HS%-LtHYW?Dbw=cE9FK-}c{c!eG-sKFY^R*w=5JH5gTirh%?%<&DIPM=fbTT+jpE z$QaxSgt8%2S$c`CFY63Vi=nf_tfE92O^6`2E_yC?IJ`2JwX&(C$;7nJcx&tM)n=e$ zrZfsTNMdXgt{tmc_I?FpM2EY*9~l$~XlZi{&vL!%bp+`P?t7GwbF}Z^HRt2d_45^z z)#{sEm~-jG#dqHhy{lP+2U~xaPhLFfqCH7Q&jqR^v(+SuSp@HPO7xo=Y?vx{25f(I zNU!}aaQpUD^b(>$_DRoUjirHb_oQRe*{g{9{k3ex6gtJ=RuYLglP{8Nwslf_q5KA) znR_F`9NOuwQbj)?{Znp7NF0Gg=ZkIyETZxj#wE*{!(UdHV-rnIjtQ@iaI+~tdCRG* zlt91dm*(vY-K?vdVz!(pwW_hJ+cgn+l9=&+-)TbfJ`46(;B=#K>pp8Q5RgrK?nYfq z42lvm=rfwF@gVA0XY3Pio^7xRtI1G494e(LRo+_$C=_U+#; z-KT6;T1^YI(#m>{tZYHoAEvS1B2Ku6zv{naIsW>(brukZf=vI$;V++^`}h>2<8SzU z&_13y!u!}mYIiI1KP*6c`RBgd7T1FWqRiFZj#fbZSUhE#Wg~V=YnB6oid)HuJmTy3 zv4{1!r^Z&h2(+pEj2A!KERzWo>VQqicnvZ@(yIvT!c4QzJ~GM4y4BbAr4cp{YMNi^ z)#Wk`2XJ^|Nm8lxKD^Oe8zwFqIgC<-z+%|Hz zKCYd~EDzfGxgIjv9sFLy{U~YjVkstOI7@&yL>Cg)iaj%)gCo5X`{ctEmu++y+^T4T z&kzr=eY3T`=2u-+g)0x9l}}F!$L@CR5})-B7b-+79e+f`NYv>yPJWR5Al2k!gg|zl zY@s6i`yW78D<4z{RRl+mVqQznkK{9oC+}h!=B#B+AiEd)P0F2&vG+VS6nIlvqr+ym zJFF)b9K7~G+9YkoFqjXTR1%(T*GCThSY5tbSyvvLPq=3}N8G=7;PTCQ^ca{@16Az=4%mAagea2BEIb`yIdi4eOwLY?o~2hnq#5BLWmQy(;>}z zZc4HE5Q^AK@^H8t?lexa?QUuhXZJ_B615eN%w@C=50(XC2*aU;UVM>lwev+%c~OTC-_c9rrFu16YcPH%xAtMYpw3qgGGxaMD~A zQWsiOrbX5M*=oc7kV{1FG8VxNLp2l5qluqxs0?*@Sqgi=yhjg~ZSoh+477DK*s%0A zTdx@&e>?CGs4nEx1Ir9hEB*HgtWIobl=fKexe^nNBnZ!G1#w*;P$R(=UZjjCvgr41 zeYUZG*crJ7ENvrMJV4Ojp_AiPL_WDn>D_x^x$@envp|?=U@r~050uEkPV|>bjM#zN zO8&j6qbqFC51C#EgJ8!>YuHEKuQ7IJ+`?>V{Tn`2=&jbr5b$0_>0PUX%zNM(AP*-j zm&F8lepueji;X#3rBE!JxGT*_>OpQ1XWYY;ZHeV27_ns9%~hjiy6N3wdk8XH1&->$ zrY{)cKfu3_8KY?LlA2)-9ovf8M;VzOLn2G);Cp4z=fqv@*`+*Huhq%w0Q$y{)iWk1 z0b}}x?3HLPG&WpF6-`_fB$&Pe(uTE@&9JH}*u?!ymiL&7eF}0b1YI@1E~UsQnJil` z_F_nMm@tLKkq|CT$Mwq}Q@;(YjwhQb`%VQxY9;)2DdE2$l773Z`7cX=dn`$arSJj| z^jY&TE0MVSvwO7FP0D^3v)kb+R4Oh5E#!7N{X0ks9W-Z7X9QTROH;{ z;e`+DD}xfw1(muMz;x3jymd&~;e{4Zk0-8{ugKTWb+WDP18b<>1I5GWgR^hgMP7_> zIHYPBg@vc*1qb4*I+!>8ls}cRUeT30#(TF|$~xzSh0pl~?c_`KNx4PLc8yw$J6AT= zH{E>#Hoo{=G68eEsMWL?+L~I_JS8XES`8?CH{)3cQ-H%77hhg%gU@H27zl}ian*?y zy%akdF&kvMQKewC(Gi7&g=;~MmnY(6`=o{g4!^FHZP5=B`yV5+|1(7l1_bOB8}~E8 zCo5H-O(7dzh=6AUlboRwF`UoACLBu(6f5E^Qfs|o!X9|RkFS7IUTl#c=w^S{tk4l8 ztZH!^QFf%BkhyV@)GN)e{9|4BBs)wp=|bwmQl6RvrzBai&fqWY)b-`gUpkCon~0Ri zbb7#Q?H8owo%i-BwUZ<&?+q7N4!Vuf`80z~`Zp^tC}f-wi&+*;b{#j88wiUDP&j|c zi08?L&DQD|fV_w0%G`=d?vv8qs%n(f23h&ZRNDaM3l_Vu?#(gAO9d1CTeW43=fm zSz-#jc^#X^b^rYJ@w-2rhEc|I0pB;H(kd};N2@%rF-U>}H2g+y*4vIcg?qkn7(#$7 zLLib4Yyx?c?Z0?nznLND8ZXv^FVa!ge^oM%#T_6HKIkA!^4`=Q54m>MJ&JX{-QvD# z%19uQgPX8Px7498P_=VUCcqic#dl1gXaDlJ{lXA}L%>%D=bUt~LJnz0E_zy~Xb*|u)k$3d+L(f7;I zY!m^dt(ww z-54b2Eq21HpEJbSmS9irfsao|~?a>##V zG1&a{VyVA{->Mshyz<`f6!yD{FN^4AH}jdE5wYDVGy7$EB)CoL3)$1V+hZU?OtyRk ziJ`Dp!x^br)d>zkLJ&E>+P-v&9oaWk$JT|gYkH8vHKVjT#@*G(Ku~Mh?9JOp#$~X2 zM`!0GqdEWXAPXZ%{wAJeVYg|3k~kp&8^Lab&WzohM5KiaTkYUEul5QijoFrFU+_Wo ziNfcOi>N0l6Gl=9ccA+QL3T8e1!Tyki)Q*<}8UtOOUw$hj` zZ3?)!Au8YRkV73Ndr}QA=q)5`FzzB(@^^)NIMX z73BtI*Y&h{XxU*J=rQtS^02R|IpD&8ca_3861BS0__BH?i$EP^*BWJqI39Pejyeg* zY&kv_*p#B$g&5*lUk1E-f(hIu7~`S7zd|@@Ehtx4l5M1J<+&YMoDiHBqqqhWI*$+E zw-D!gJ8q#c0mE9Rlup#w5U3%nk36+Wut&Ye)i=mTT3#fMX^rD;fm+LJ@cN+(t1GR* zj^m5@tE)(@lI9^y@bE>NLT>Km1`0f@O#ow^4ESK)EHcJO>j2p1AN%F7AAH{aXs|>F zIj$t_)4?J2*(%kr7qM0-cf+acy%w&y{Cg%ku{#2WI~KDp4N&(r%Z_Hk?LTXwUmad3 z5f514h~)MU@00h@C?fJeW&7Ki7{(CG#MK)rq=+4O4$@VaxLekhwP}o^vzLVh8%^47 z;xel4Vd*Xdr_pye6+^58zdHQ)jmGo0b{3R(J8HJP1nnOI6S+-?m(khGbO`*|TE~0g zkye-m>=)P@{0I^Y?CB?&dp(7l?+L#r8f3*qiU^#wnqVoa(avw?Y(}bH;(QSe?6; z*h8=7ao+lz>8dwV3e%uluZVPaan3re+o??`3;dg0ONMK}361K&TJ?2{*?lvoRgclw zxiJ?fx5Qw1pV**Z2Z5Ni;vpQ)4S0)u_?SrZyt%}DOh?Tz^z=yoK8Y!T^~BR+*qJtZ zG^32zuTJ{vMe08fT@Boiy^HBT`c!j$i^x?=1h3qPp0`=pjCkc{s|z-b4Ihan3y8r3 z$8OJIW%&*4tNDiowL!HbDC4blA}J*6lEo@S_B?uc_u{Tf-N~Xx>{Z01+u_^@DXKlV z@N|m5=?9)l zNIMDW{Yi6rv7?W#7Uh>r7D3LN53_FjLsuK@eW#t=ceuiwv@`vNSe6?vlOETXQ3H%$ zyzjOe8FW#stb1UsJm+YNd59>K(4sh`Iw!G#If_S4Zh3W7?mm;f8)XO3D|RoHfTcs& zD-L^&o)c-(uspPCwCY|yo!OZyOq>8IVuOM!I*^_{o=q!P6lDEI34Q(EE_iu@ zRchK|0Cjr6{ifOlK*%&Nt7)j!dx*BVs2_k%>n>Np*XZx~?AZ5O&|!EMS}lKZrJDpd z{>LaLC;`;q?^CeE>Hh*Ona#nSqs|s4l^%BQ63oG{r!udpPcQC{`>Lhf>fxCcr(21Xf3j%9QL?h_rVlD^BtHI5^(Z8U=Jd8c6>XKG zlasdYVhDiozSjQ|u>YOW^#F^vo!5p9Agzig{y|nFmA?Qp;h}cFu_Y|>L(SejXLU!% zv`yo8uar`#OwG(}`>^gk#e>?vlM|i6WHfiCzCm^5lvbBCNV?hYC^ z&p{{lveK(tHtdz{>LiVlkmKX^VD3v47FSY@tVD2#U1ZQ+3!K&;-DSZ_3Gk_Jo{Rk3 zhiGHSr}CP;^Wx8c+&oW4p-t15Dx<8xyz?$9*tI!f?=w9b0JTL=L2c=OiC~T^T?q=O zsPfB%EN{k2{eL?*c}493s2*BuFDO#CtYoql*xNhAq3qK6n2oCWmZ;tO(}UG$9XkN> zM{qAqF)OLg72T_))cBVarhJ5tTSBw&_f|EU+wXLIA#wg%>jW>2qC30GfA3^n?EBxx zl_kHI+K%0e>@6*;a*{O!U~%qH0tRZO5KMATlW6*CrdN52@w=8v$bR}47dA39xZUd_ zXN1&|{z_`g9fc<}rjMv~-xowvW3~O$P zcwSyj{1Ofdx@CowR|hm9ILQyU013gOPmhFTW5K`Zw7NK4T*|bsTvbw1GOdU8)o%Eg zOd0QoKKOQx(abGF6bp|op?>j?cQ$hZ8Vfo=WAWYy>XG$e9M1Xvv==)n|6?yqsAZfI zIK-XW?WCP2?}FQ);UU@FI|rRsT`MP9>+)HrGKNLyIs#-J|F5j#R={v$@=Lf;VVt@xCAflYeIcc$(Bm&>U#-bGO)+w!PD*XBC|j=!@X>j7N-w!*F@ zo#?;X3LnjwEjH%*Z%QVfN!MhGy0N9Z&K|_w+E~SXW1e7fiE8(OTPB41JHw|~|2-f@ zj8RfWFwoow#wR!Af1#~Fh3?%j=g^2@=i^cp>L0M1?xW`gbRRP-Ry{`UgNgp58RGYE zsRPNwE|02()z zMzehDnesBRNg^L9uGdRuL%s>8`nStXVxHWhq(-41U#@DWic*7gb+IOZF7d*{g#s zyz@pk{!3A0|3^_&yS-_DqXtMb)n)W~4{L(5MrMyb#BRmY;(QQ*0IGS8xi^d)ix#97 zF#|^}aYWK3!AL~)oYKeqLPE7!44yJvLsL_nJFM6Lqk*#jkaj$o*@kq>yoH<-KRcuw zZ_;N!?YRDrRHQ7*kgoK7C>QbFv5BeS?Q2Wd^nf}5{hZcg@;tx~-PY>Y8krioy(^J! zyS(4DJUHivx1XIJpazV3p6e1+Fw+4~{|NZQ6=;w~1orB5={a6u9W=ju|8?Rj|JNgg zf)*g7!4DVKU7vZ+lrn;-6H_2t z>jF`KyVY|w-_LDW6$Z>>_Nxo<0>G3N*+-pAYxmj>>W9Jw;wRmCPkXh4GOo`e^Ss=3 zg&Y$dDp+k>KFRUpR6UV$BR=YmEyWnLYtms$mug7$Dn~Y58bDFX9QMgakP+W|i z$)E+9WX*$T$fK6b3*yvm!+<&whT`qgP@a&Opn*$ZjR-QIoU0~N{gmYRxNt%7@B%>V zSf)U&>ScyF`#li?V%yC7_{Toj~aELSnXQb5Unqu zc|uT+E1Z?PtOWA!;`CMn7N2Snf}6{Lq^C=nF->_2s0A`QKt`fg0g8cqd?7LUnkF~@ z^_P)yG}Xtzs?qO1^msxP^t8!l1GUJYep&2iQ^WzAc~Pt?Fah86j(fxtcMT{5V$ywe z0iOnJ_+(_Gg!qAlub!==6dlk#_L?rXcwmzo|T2MP*ozgJm)e-{{-&k{-}_vx+Y z&)=Tayvsqc%E$5P4px$UeGZEkZA?6u8~b-i{|@QjDE%9y|HmZWropH05+4J4!^Grd zjgmxlNA(i5cgN1})YR9cg4Z#vBtrZZs9}x>y`yu2p(BIi=n00$)s=W+)_S0Sg8UWS zpceVL{;r0%M(w+W;X|q+-NDrSwCvv+*zqx_>EfCcEs#zbYucDcfpj{;snco#q|=)B z+#Rd3KshChMBBa>ki&a9v$(nS3{1JNEg9(PnsP4{Ch6@(dzxF;d{Gh>5K1|7Qt`+u zO(nnkHE?DxESff*URYSyL~7l|;Bi_)(Lh0>Y6kq@Od`A?Xo@>Fh3~kbjomsRA=VfieEc9_sK)3OV_}^?rHL_ zqf{(YYFE-SrlgM$JD4&&Z~>A2}XdoZvE6@HqQ$ zule9+a8sSum?%oCkLYmdy^NVIC@fSl%`huahX^)wOjpm$jPOMZ?JzM92wlXa4+!lP zerQrawJ2Y@q^tV;Z2xOx0!Qnh7{L$dqnzb`Ywx|In%uUxQAI>V5p1AHvw?K!(m@nN zK$IT3bO=QV0U>k*RGRc&1Oe&21qe-gFA2RUozP5xKtjm(vd`Z8?DLKB8{fV6uY1Qm z{*iZpVYNBeoX?zdz0Z0U3?zF$#5!8ui2#x(ogvl$cMib+1oxHYyGCjMb7Z#NtB3G1 z{j;~a;hauB;>c0zbLLvsvv1{@>YN3f+2xrpfk)pPdRqqcz%m9Sz`@5C?@WZ%ZR>4k z7JYzB13509eh+Si5Ft^~JEL{P!8l7BJL{;9oS*|4WFhat(0$Pr(f-(@ek|Sb;4#hs zc$re^J>4W&dQkkSpmk2dJ*)w45=L+O194TB1!3{w_u;cMUWYN%9l!1XeqLYUmKq(9 z?6Kkphl*8@V-WB5iwa}CZxXZN<=VhodPz)OsZv0a@S9pXXI^1pO#VyZ2(Eq~`jhxc za>dIHWo2)sNaI6~sKcmNcCf4Hbm~-up4$^qq5f!mjyQ0MjsC_RH0E=shmrfp)ueUt zE$bD1Y4>~>xa>ORS!IC7hLq707aPgoV{i>afo=o@DEDpg*1UudydPkvM-4n1dxvbji>3cxE(3LqT} zJT3sVJHluD9srBNt`uK=x0KRP(zA4Qw(A_>a%@umS#^+6YVNtL>WMn&_p z;%(1Pfxx$F83=C#&{bdJeP{*R+`Fw+!PyMb*k_aQP;gc4qNOMVN=wONy$x80q#bNc8Ow+<$d4N zQ@~F^!&j4jXI;i-CKir0vVg2^d<7KZKaX9k-LDK4oPPlDpRvouik#B2py+sKBPDBh zehHL%B*etVC4<%@R(M3rKVD`KdV$|ZD;8_8J$BnRNaQyQQb`p@9>33!*3(P2uq2u% zO7DP&nfl#fho|f9Q)QtgC_Uat0$F`tt(L?0fI+=YYvKSS1|oQvlFy zE<=Td_Ap2`j;{Et@O=y2t*(Qa_1|>zba(Cr(Ce$beEA~Jk$h_ES&mvVr$(--V5j8` z=4dn`h6B<;5Sd{P>(pWTn#i0DP`acD_aExv8q=PLe~@Jd#ABl8(UqB%z;GlEwQ& zJ&ep!L(`8Ug{p+it?4F@&U~~iH`WFXsnDS!Hg(7ZyEp*=i|pgyo&)TsM}UBrc`Sg1 zZC7s9r~zv2)k9&{Ep>LDzPP8H6J%K?{ESpsTpV}xevmTheE(V_@m_w8KPLAys>N%M^_0siD7(AY z6;@AesJ|&&=)}J_PY3vYDjEpe^_f25src$KQKW^3I%18o-y9waXY>SK>I420W@OV5 z47zsKT;)uP%D2VA4!b3Tg~&I{`z5$N-$GAY#nx!4oppFJyq2 z$Xwukmw{)ZCjZb4AU3;^F>0|5C_iw$wMv&2nKYlleTri4bNTeqJv~&nr94#A`30&S zXy0S|MfI4FTwsONE@JSKfLU=L z;Dw+$&YlvlH87LiyVa|CmfR!Kckt49X;QX@^%kYP8B$9-7D2=x+`A2b8Pz;f2U!lF zk$!Bbl6QwMdet-0-0f4%R!I1e%CEdvMdw(A7nNso<8Fut0p@b(!t(#9+0CmouJT}o zp3?>#kcC}I7Z79Swq8A&x51Yk+BJDOMyCv6qQ6l-q8vSFc-E_3r&|@ zgzc$3tWb$sC_o;CR$qRzD*^HVtYXD0Kps*7q4O62c}Sc4@srB~@+dHgLXM4f0oBNF zm6gvF!VUC-F5kYU;*Aj*&eO>40WQk@R#l}%<=(FA8gC)A7@p0?b5wXceVoP^)A^(o zD4DnRO9@5*5n<--t5l0I6eKW;Xfu^H8XK0*7eq z`ToNO?{6v6@L=Bzn$M?Iw+tG<)UE|E_{v5ByEOWn@-(gP2j4-KP4OgiWuB^-zLFBr z70j58Mn5+mx5IZ1PxEE!EZ`&6Se((f{qRTFYzLHGqA)D3FHLGX)S=5%J~VG87p}+l zD&=F@<`XOH4*nq{2VQW}Z+GI_^4KsFIYJ(wH@iXG{3MHlb%=?q@Q*qlYw2Ua>T{9- zR{vDJ-$WPsS{xKk3PACiiDG6sl~q-VJ}9%BcwC9FC8Wt)xbel8V)1K3EVq*FLzl!M zo}Z*&zIq<(os&Bk`vrcWScdO6JEsPBVFdK0S0_PHUAGqqXlfM zR^J0I74MT{ibP@HqGX*8WD18QCnx7O<0kJ8lj5Qxep3Ja+ho`kWG|qtp`RqJUZo0& zh*Y_Ko7u08I(v@dWxBN1{OvQq5K=O3pHfUFu%E7c?ZBNaIo4D8#^3sV7jQy%Et2ax z0P+92cAuGS{vkmBsU@31e=6?hiRMiF{lx_U4WX41pssxd={2-1<>k(KdF$+_000ix z!yn{{DDv-qRFsXlmnhm~w1Gxv12CX3Q9?&ADVg5(oAEqiq@=ndp_>Q>*KYy|6lR}f zYauiFJe(<}7*M~6xn2P&xeN%P06o64PQYWs_Y6LRg7wP-z#TnU9}yv^ySsqBQ2&Gt z)M?Z2n7{|P_t5S_64>a+@tCeunw+YonUD@Hc4I@j1Aw0;ln8~SZP4Zo(6@m5&xeC zAP~p$@^U6g`;8IrGN3d9d>B-;oRDbE3y4ORkg%w-hDJ31+?_jjrm4+|H3Q5yZ!V8? zChT5v^}=n{EHu>wcBU1L|WNVx0pimM&rG)+8%>+MEV3(^)+?KSjXr#jH9G zfHc)9aqH_)fH)N3EHrum3~#yT0s3-oQr;!kUjqOj!#-~+FDA`S!52Eugvh*nMveN6 zq2kbKtIKSd^)xbhs`JvuCJ%^hDoG%9bod>+1d(OI<*LCUvI3-Q%XuoM(~f-r)7mV!r>)iyx&T;mrgoVP)Bv1CfxPk>N0qQ zMsI5fEh0wfD%XG=FqUFNPxd#zpRe@OK5;pU{zFAGfliVB=uRFX5@LCqBQ<$ga_0&iiJu?%9}&_I>=c z50|0IwHlts;rmU~{fmw#JguPv!O&x3F9ejsc$75IM! zs({JrRgCU?Zrk^2=}IT4GHH=}%-87Mx4_93J6EBCw+*}e42XnQ-Y0-mGHT!50Ya(T zlKfw%J?ybv!28Soi+U3vhXvSsXYV%ShnZ{}D9A1WiOZaOtV1EE_yS`awQuUtxJ_j? z>E$sL3d@nMT1&R{G?MY`xnp)Q8Yp86?(|KC1M0c>Ofvyc&jfcXlmmd}>z9?lv};j- z$E9ZjAwtGQqYK$9y#PqOu0%)lDRNBuoxM#QWW0Yl5@>@T-8cC+`Qnh`N08M(@o5`u zFJZK6{(rWi{Qta-Q+i&>2MftcsO!%Vo@ajOZvWpL#Qz%_*p6kG@FeV8OhkorONmPT zYG^9Q2#66yWgwAkP++ zm6fyMjluHE?U9YuK^~-**+3`gB3A*;myiO4EMfo;$O-0Qm)NYU zdn@RL;XWd$(j+4&sBg0ZJt?lsl%~hT#Po0tA|9&A97a|61F$ftyz%aM(z@LTTl|J< zXvnLR=)R(4tEaA$K45^jEGn3El44hdRVW`|D+sClcSAQ~A2j zjFoY>j3aDjX6#Md;m;-+uB>r=B?=m*MS}AC$CSfkLe!3; zh58(CNM$~C6KvYsyCt^n-?r3EFplBpYvK!`VY%0t2K7B&sYHi;C5B*WVv|c6bfi5A zf*^34mieeXDiX}s&{j#RsGy0dCmny`&k;Rt(Zj^|Zqr3xn{?XYS(+V}5n-E11O@>x zbh&l(@lFjkCk3u#5z{<}uVOkww!bJ{&q)&Fu4sK%A?Q(au7;m&I+~{8kyn0!`Cwa( zDC5ew=##wH^D5P|7|^k(%Z z1J@2?fXN^PxzGdV<CO< ziGZF6L~~Lk{T0kI0q}4pJLck%3gC;mE=F&L_J&6#m*9gvg&oQn@gY*7pu&1uHN@^a zvDJivsisVynYs(z0d7+Fu`6S}gM(r(2Bh&l2dnvbO@=mI7Tb?SIHK@UQ18i{12+jU zLraBR;jxO_?yu(X^T9dPUfd#f87c2h?#}O3WN?ms_rSNr_d9f%4T#GQNF8uucL1wm z+mX9cnFMD)#=kcE7@_C%LCog>q0bzvHb)rQ^gt=f%z6>S${3Mqokwfi=qeEdFpPQU0eeV(rmI0IFCJ`xSfh z!9095CnxEaI;69m+ol80P_sl0MAF?s$-Q#-#Ot0M2s8R=rS5Z& zb44^LB7T?&Z)MMz$S4 zY7U5oJ(8agmm>l1!CcXk((v#ddP}sM;!g4E*5Ni{eI7H@R??%c%(HABDzQ@&+n21* zM{?iO62oG$!koaa57WICp33~v0%_eP)h;_FVvs^&zhWAfTCbM~644F|_HNUf+InU& zM=)rUv;btv?g;C+W3=`tG&}A#o6LlbABV^ZR>^t3KO)Jno50dov0kzJWb+40Z+(fE zU0-#Z+FUQ=)r^RTK{Pg``t5w9Ef`lq$voO@&x5O%P2WeYl4e%egT$n!WMSc6Usl*@ z?(&DIuUMq2bz<$1U5Mkprmh*d$E0WQ{xa-Nk*zCO8oR)SN$)g*MqRM~W)ijKH4$N+ z-sa|ta$ult#9#`Xch3fA5AQ$qI*0=r>{^RZ+DM$!CtBYhHf(~+AC_MC=2qrw`X(eK zl<2fBHV3in7wjlzK={-yxF+KFFJn`-d&b3idAOp>N7O;i>u;SquEz72a~FJ?22nFR zVT>>|S z&Um%SZ*lV-jsoxQPS$j7UvM;Y)ol9x3$`MbTEJZLwR6xV$4R&(+Vi_*xERC-_sm&J*)wFczQC5gx6liH{&F-|pYjQC|HUTx{V-Wp0(sQqZiS*J2Cj2HT<$9^ z@xTI=Sh^Z)Om?#yzE7C^TDz8&)lx9Her1_uvmsJUJk#4Kz(zg-t~;j7TdAR~4VW=_ z-lX4gbE=wGh1t=@!EBR^3_%t$_9K*|0-!gkXQ~YI{N4S-6;c3Dqd@CP{PhWt1Y@6{ zv#(sa6>(XM<5&yFIy{RZUEi1X3*BmJ^EwW78`6op?c=XL_twnDzhaAZzOQ7RGZ7Om z33YToDbukCZmj-Nf3lZxMAw`aY|j_XrV_cz0^5?{Lt7tiT)mlVh`(O9(V%aE`C+3F z;E~di+2P!&AdXtV)#$4s7-XKOd9B^No~dYNSJR1G-oVn`nn=Cf+ZKP@(p98@qc8$s z43dabEU7P_dEf4DNbs-=W9Y?GQ_T7i*DowCwX@SRI+YPxrjsBEm+aS?pkuYn1<^}c zS(Z5iOiizKJVpz4*AD0Lj+aP{MZ_Jv#<#6OFKqp{gDvhEhjB04&6+ktq3Xrx;VqYh zTo>!g5O?qRmP{4#bmgD9dW-xZ^E4*!ovMPm_C(y%$Mi`!{{g$sX`A%t9n&DY&h+vv z&ZgoO!^+g^K(ILHywn}F-{RW>!AxhXCXw@Q`!m>M-js@uxX?h`uP|)2MdJ``Sk%yq zFy)Ayo`f0b?7Vf_e_fcfSZT&o|C-5Dv?kD19L7?dZKI9e`I(gYV4jqS>(A;R{z2x{pC?x326#p z2J$g+3}F)aB0<`7glGmbi`3$M$yE z*$v-)7wZMNMQseI&9>ypy!&3CW(@nu1X-zIrr8a_UWscW9^I`tH{E{DZ1Q}2;Pi{9 zg6#@T2 zekXzD0=G57gd1JE-!Xz*<#}QT8Wdq^Jm~@O%8rn@kyF?k0OFiDc>hq3h&cp@uSfBA+C>l5q6nKW5_1`-l1KbiL#+PW$OuKg1@0&`w;gtzJkqJ*#G18$- z0?Vz@&Myxsx+7>N2aXSQ0hU@ZPI(_Y$6=;kTX7bTR^}DOkTzXBm z_qONl>LpX>o&JcPR|E%+tZ~Ec6c&9^aQF;tGz+u;!)B!uckskqe_dx(Cn+gusijGZ zi=~NhbjlY3+dA(f9E9B&fDeai464teyO_+ougdp7>GTp&Iys3~?2`_+oC7 z;iMP1U>T9j!j=CoY%Uc2z;G()KjkG0$m{eV7419O3{h2q3vJn-638ESo03&fDhkql zF%A0usYn#y7Fww}S<5)duwbAOil%_#Km124<IvzJ#%eXA^ZTdqMKYp!hMx=!3kpeDJ*MJL6t$4+^kp0 z{`NelZIPcINL$+zbf@%GJ`>G7kMvbqk#4f?kkUxkdnPDt5m5H0oBC6KrLQ&X9`JL8 zUL@n7ZGND9)`QRWlhO*aNRrdwST5agsM@3n_o-L=TitylPK#fCRA&CNRKS|4`_6?J znnu+HhXI+-{k-Qwiy9)gF8%xKz+W!h2QuL-6|D)Ur5cs;8yPs$_yqIqw^=|U*~|87 z|7ivLKPPec;*8m3XW?Tu%5Sp3B+jojPCR5wgiZW+8vm8Xzd+XipOgwi+M31bXzDa| z{@%Gk5oCF`kkT~bAwyVWazgG5lr&CggX~1xZa$>jWVmi-sv{dCO)MT<5sQ8+sr!ra z&iiz3dP+g718*-}V&T4+F^<*u3>oF0AHES}TSLXlQs)x#Nbu?Rm^^Y}=8N47T)$PXa#1<0tR_qqXMXOSIfRwRf## zP4p-q6foFM*mEbo8+!h2bnYt?ZdUKXMRfS}<|-fK^ZF(BeHj7AjlF3eX7AI=aP+z`$Lk-tJ@`F-K9uAFSkKJe&lH4ymh+u9NUVOuUi|RkxR|4AS0Etk9HUFS| z@JlIKUt->peHFwlj(_W2rrQTp!|VrA#IzOZu|ID7qAtmN)jKz5gkIkk*~%@vA3r$N z`*ZccbS*KvqAgmwBT?u~@oiE;zwn|@*sZ(&(GNAyPu?4Lg-^}jF7+u5R7_T33q^)? z8I>iOD}sU)z}D99SPpdz4W-r>Q?}2&$W}*p z@D2usJ1Wzb52CB$R8XE{Wqlu_X=oxQc6Yx_&t*21%qythH_IssU-BE{f{Be-w5^{c z$_gy6bO{TVJzPvm)O9qX{->&176Yo&~Yr zD9#rSw0^62?~T2%Ff#7NgF^8)_MbLB5Tp+^Cf^)Es$InG+hy`9vqv~^xX7zss)1KD zTbwurm*9~X1ieI_>6FWyFKT{KYdII%C9L81yo2MPU7qoacpzJP9BCrPLq?hKJd3~J z(>O>~D;4sA=pmod;(QX(Ikvv%)f6r}M_I@UFSTqHFpZ;$8 zpgwOhlqD%1Ic_)LTvwPEEs)%+Cmvho!e+6qIW>}4IXSVi$e+b^10xc3yHeA&E2u(# zn-Ezj9#_HQPM0#rezy2FQ2#~UHis4?{%Vt{B57-3JV19}x=o=(IjE@#Bz00}^r74X^j;h5~8n z(#i?f4Q}JEdRkrMkS2%>U+vJ7^nMUqeAQEFl95^e%7_E{vx>BRqFs-`Q4C=N9#sL{ zg3Ze;SwUWFS!-#D`fOGGm*G7%!H;i}Q^w^!jlFfBOi+pSTG@xz71OnpQeDl|Zl@4M zg{F<2798)Bv~Tx~yFk+E6?S*Qb)G0FJsRevwd$;W7UR>ps3~os{a(Ho^Pt}^K9z#Z zKyWnUqsrNTDkN=!2VmkgN-RTfoh5%seveMrKK3V>RY;w^$jRnQ-lMt9tkfP`Rf>OJ4!Y#az^UB`a2%Ut&Exz&`#doZak-$`P^xALn{c|s~F65qR zDNl#K)%@r5{^1yWYk)D##Rd8O(_8%83!NeJrlRF0fanzh|M|;*H1?iLwsh0p=GK3h z!hblj{|?~4EAZbH`0on*cLn~xwgQi<&gU0guZEGX5Z3#i-&tcxnTme92blim8`oc; z*zCvW>|GC*-0v_lCYfeg)&A)d{^2_R_>IPElhw4MgQees1Y!+Aq{b$EA4j9hmRd$M z-mDg6AJ7+1}KY4RF_cVdF&yDxI_gwxmi&gMx#2u6@##HZDybcbu-sHLQkq zT5Z>@4!Y6}I<4&*NnmC#;&5?C{nZXc+k*W3a?h_O5iHV9B|498bTq7_5hXVJy0LM+ z%Q-O~c}^3}^T>%{CH89mFSz*|QZyg2FWj5`$yH+R{tX&r1>7_SlLVK*B$TO11rneCVC z+Vd@3k2^^-3Qq_Ed?!$&FS}JW~A}v2~NK^(1yY zCA2I;bl_#bO#?P~_QnuQemCvfK?4nSoc=T&h?@bjBkZ$G+>u8^7DA*Butmmmx6(+l z&^E~5c+}ql^%sE9G*}o_MRTVQPVX1g7}Q{@QfPCCV_Bm%Ktgmen07^h@|8%@d5%K zzZC(aF<>>Ce{D5vJ?7nrole+RO!e8GnXGngW5B_VW{!>=ep}Vq4U8m)LXB!|v2>La zzfgh21bt>27D?CY><$6ofVlIb_wjBX(G};Wv6>kQ^YAc+A>ci${f=c?;JEtL(9mz2 zzm9sfyGWD$MeaqqlRr1a1yahD>5FUY;v$}}a!SQj@>D|pmcf+w}J6;NN~ z#zVt0bT8GGzx}}K$OF5^9!~%Vw0=w z-0SrnyQdh&*IBC5#vKjNmlw+tkfLMtdfl&~5^nfk2|n(xH}<8`77b$OS1+Nqr{`Gq z4olJ8g|3Mz9fmY|f@qP#GDVv7iK<>vd1{weV#o*Jlvq^X(89a20%<$d^Q2#Qjc(R> zU2ui_Jak&z;BgC!Zz5R;f-cp+@*hWnZK(tMdA<{ppaqQabLlQThV{-B?*mldddQnK z!br=12ezCXvZV$}ssn!k=2r{N^z~ZOInGY78A=ZC4@RQ9)zvJZwZ_3FFXRm0qT z+*($q!&$#iHu}`h)_P3C>pcvtb6+d3j%hPe#brf)6t3!n$|bk1R-tw3c2|dV2%BSN z6}*(vhwVo6mboJ~VB4g{ry5Y@9f{iBa8EbSzSrhC;kJRpNgAu|1;xGi<3#25UFEMi zWSH23xVl~aufOKbWvx<$1_uS%x949W><>uMn|S|5N)xuxS!VvrrK>v=M0fr%tw)EL zc4hhjieUee?QMDoPmJahBSQ@)@wf%CDJ%U5Ez$3`h5SZM9-}uN@EVtm5e>fn>_PU< zR?}0}(DSA_))dc|m7KT_K^tgj!fK0M0=tx_^ot1YH3b0> z{LV|WBjXB1s(vs@_IvCG4Phq!{H8yil-~OM{GxJv)lK=W5tF0lc$iT^or4N_u=r@Y z%$DY^_ccS>Aooeiy3CC8{6&Pt0rZNE?nH$zF9DEPqqK?DX zrPQ}%7s*$mij5FI(@OS9lW}UZgfkcZ3IkuCrj1_OI`jUSwtGirOvHXchoje-shxWR zmLa*kwZ0C^8y3ZAjoBKixSK{Cl^PeequB>HC!<%*s_7{Ybg#e_kn6c7FZ7McTb!li z-R_39A_9!JonYKt?~k{}VW|CvPAKE{WX&k_?Ul=y>l{``vV;I4x!#R@WWtRHdG&8r zM-T%Wx|+FWP}SCZk>5FtLCD55zg69FnS3VD+OE`cczg`)?PL-~aMNm=9 zr1Hhp^GNy2GeyZgDooEqmuF{pr^)QNGR?_Z-}qpf-9g2Fvw@R5r75EwaWYQBYeyv> z*OoSqaQOr+w01L`+HmG2mGYg}+G0{RkTOtQT2AI=rIyfn9PExwwjYgCvwvr@*LJ5W zezyoMqyK;b3b{?~m0VclRb~i(hlIA;%1jibiq539$Q>3J&(EdB^HPQ-)VZ$@iEvSE zBZZ4tje?)94PbQ&?bojX@Tl|xjzw&~%}2sy-Urfm@_XJGz71g#*!Gy)+R!=C4IDB9 zmo?QYR>0+hF9aKwavMW6(3B7z>-=PHDnDqOJjDhlGVDU&sJOogNc2*xE%TUOgx1GA zu(BxaQo)&;N;43YuOvFH<~vl5zw1gX*nMN_O+V7l7?CE-T=X-om>vLG8v4X;s_~*? z9ws3j&NwRCxerbrQABJIn}e?Wa^x%XFs!AHy2&hxA3wWW2DhtHtY}s6DmJ3MwITUd|1Eg{rT?eRb{YoV`cXaW`*pHE5R>7G1fq!lA2;E3d}9U<1#%vP}JY%H)UN6%KT5 zH#r(ro2kA6=r(h#Yg!z_(*0xMeAvn6;A6)aQ;&8_67@3YxwIu-(0~3tjjI2~hx{vYuchz| zS^5Fw=x2AcjnsbTZ{FnY#q>69)$Aa|HX0Dbru$|cSw-qSEcy)GT<;`ZbW`WE-d3PS zhZeqEiVHa6vZH~FAKaocq31DL>p-}++0y^Yuh0ZP?VK<<+E3#0r3o*)YZt;}qpx{6 zPK{Xt?O+!(t~98{{+`yiK*C7hxC{Fn7xJs=#v|iFxzfULJ^T3#K=U-s@i`_(UVmuJ z3gaId^Ulv_5-MV})#`82&(Q~n&>F3w^A9n4-t7n{yl0+^^s3;D2fX zB7kp2b$>&LkBQL04tBVMLVm%UwLGHq=5RvlY}GNT%l%r~GB7#XOI2=Uk7wYSr98+1FMqatG!>B z>Uz}jj#Q-b3L&*`=}45+(-Mv+m3fD*v^qoKL4kFCPO7S5JX^ZIR`N}BW~ZstZ+^JI z)cbaDoNs0y3t%73HKm=6?vd%pl3rFiYfE}R(QBe_*57O0@()sA_EUV9pbtFO?e@nS%+e`u2 z)-J%|@#V`C`&?Pmu3gx@PY{PeBX>n^^%-rsMXr(~q6{-#a3+sbnzZ|hZI*Z*vs`Dn zgB_z}!)lfII)$GDg|*j)-jH7O8=KO?Qb{{0@N6T8E-H=&UX}@#crxeK7%G~JF*&l| zq1ou6sUK*y)nNdFgoNVA-?&CMj*p&IEw zG?Jv;_k{MuL5;3j{JPAJ4m;PlD{gjN#;5aXJpUsvY~p{OGLHRLO1%8Peq^JJ@qV9v zNgGx8js@kq-S?c}NnpEEVg_Q-#T`|v7Po2s+;MC=F}f@|&#rsLNQ1=`*H1cG=&E|| z>#rKFPS&n}XY5SCqoKHK?HG#RwRGg)Cx3v1| z>)_m04rrCFQPYX@;0?A|X8e#y;Cno4b7w?a-L`FDE(3%&-tFuB@&H)Gr=?-&vFKyO zu10RRv0t`$2huO^j4lC#0L)KUKCiKqn&Hw0o?6;hWpPaG<|fz(e@1rFw#2TEf~iUl z=d#`~7FJ07u+Q%+WhgapY|qq76?rqJ$pg9S|2i;^>Lavmp<+-5N|?0`GeEfXL$yjs zd)Mh7fC=HxGJA?$=H40MWu>?3XGUky;;s>%u$l8+&M5E74F#P?1xX*Is$G~jK5K^o zFl{DyrSbhR`soVmH=Tt1jItR+{qK)z$L4yd6mDj&V8q6y3QP6l@>OZogOCk-@;9oA zT2_kg@TB(W`WQDo%)ef&-2z8?aFmRGVi~NZ)kCYv!O7gEtR{QHP2c6cld_W%9dPh8 zD=nnyn{l%qPp~)mr;d1d{()*t2ux5pw6W&1dsAS%_IE#2=#|+=c|`?P10myf=I_n> zf^o4Wc_U!&c;ONy zzzc<^nTSfd*Il`1R9O&oo52!A>;?j2P+qPsO_QQh^`GxcA9ktmo1}SPeDuW8UVo{Y zZ`gY@7%>A@M993&_S+8*)lEfAF$cA&o%Qlv+R|^T-BNk-SHSA`63Bb&s7FKo%!|vu zk!4E2ui_WCxFikp#+d3%_XT<_AjPBF)4V|PeUfa$9OcRcAI`Fu+&k+T`{afmINi>R z@?NkH*;Kk7tRLN`BKGgLfm)D`UJUlnwtcS;%>mc&>P{Pzvyfe&{$pZ5g%2EE4c-xv zfMFV@*@Djfl^gh8d8WlV`?(m)U-<4XU;9`7r61Twx$b_6%>Tm|UfZ1F!4PNZf0S|m z8rf+lCP~2P)UafNQvbsjGDd)sK`h;}=bt_PZv*gc7NTd>OG3~&K{gv0A{S53 k(LcNWM{&r{hIf~w=w2GxTJ@Z82KcA+{MEDKrzZaY3%r2CF#rGn literal 46616 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 z%0D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