Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b61dc8f
Add proxy_ssl_verify and related nginx directives to OIDC configuration
pdabelf5 Nov 14, 2025
d3c58cf
Add OIDC template parsing and update tests
pdabelf5 Nov 18, 2025
1c40663
Add new OIDC TLS verification values to Policy CRD
pdabelf5 Nov 18, 2025
53ca87a
Extend virtualserver to render oidc tls verification parameters
pdabelf5 Nov 19, 2025
8b2359e
Extend the OIDC example to include HTTPS on Keycloak
pdabelf5 Nov 19, 2025
4ffa0f1
Add template tests
pdabelf5 Nov 19, 2025
cca7ad2
refactor OS calls to manager package
pdabelf5 Nov 19, 2025
6f665a7
Add GenerateVirtualServerConfig tests
pdabelf5 Nov 19, 2025
dece5dc
Expand OIDC e2e tests with https keycloak
pdabelf5 Nov 20, 2025
2782d3a
Update PKCE example
pdabelf5 Nov 21, 2025
d1a95b5
update snap tests to set vs namespace/name in oidc config file import
pdabelf5 Nov 21, 2025
0251e29
Merge branch 'main' into feat/oidc-idp-template
pdabelf5 Nov 24, 2025
3b00462
Ensure snap output contains valid config
pdabelf5 Nov 24, 2025
9d49e35
Update tests/suite/test_oidc.py
pdabelf5 Nov 24, 2025
35bf747
Ensure snap output contains valid config
pdabelf5 Nov 24, 2025
493294d
Merge branch 'feat/oidc-idp-template' of github.com:nginx/kubernetes-…
pdabelf5 Nov 24, 2025
b9827ac
Update template test with VS name/namespace
pdabelf5 Nov 24, 2025
31a94e6
Merge branch 'main' into feat/oidc-idp-template
pdabelf5 Nov 25, 2025
cbb9a8e
Merge branch 'main' into feat/oidc-idp-template
pdabelf5 Nov 25, 2025
63a97f3
Move policy validation to the CRD
pdabelf5 Nov 26, 2025
0ffb7a0
Merge branch 'main' into feat/oidc-idp-template
pdabelf5 Nov 26, 2025
aa3e4a7
update crds
pdabelf5 Nov 26, 2025
831e7c9
Merge branch 'feat/oidc-idp-template' of github.com:nginx/kubernetes-…
pdabelf5 Nov 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@
# 101 is nginx
USER 101

LABEL org.opencontainers.image.version="${IC_VERSION}" \

Check warning on line 611 in build/Dockerfile

View workflow job for this annotation

GitHub Actions / Build Artifacts / Build Docker OSS (ubi, linux/arm64, linux/amd64) / OSS ubi linux/arm64, linux/amd64

Variables should be defined before their use

UndefinedVar: Usage of undefined variable '$NGINX_VERSION' More info: https://docs.docker.com/go/dockerfile/rule/undefined-var/
org.opencontainers.image.documentation=https://docs.nginx.com/nginx-ingress-controller \
org.opencontainers.image.vendor="NGINX Inc <kubernetes@nginx.com>" \
org.nginx.kic.image.build.target="${TARGETPLATFORM}" \
Expand Down Expand Up @@ -662,8 +662,12 @@
COPY --link --chown=101:0 nginx-ingress /
# root is required for `setcap` invocation
USER 0
RUN --mount=type=bind,target=/tmp [ -z "${BUILD_OS##*plus*}" ] && PLUS=-plus; cp -a /tmp/internal/configs/version1/nginx$PLUS.ingress.tmpl /tmp/internal/configs/version1/nginx$PLUS.tmpl \
/tmp/internal/configs/version2/nginx$PLUS.virtualserver.tmpl /tmp/internal/configs/version2/nginx$PLUS.transportserver.tmpl / \
RUN --mount=type=bind,target=/tmp if [ -z "${BUILD_OS##*plus*}" ]; then PLUS=-plus; fi \
&& cp -a /tmp/internal/configs/version1/nginx$PLUS.ingress.tmpl \
/tmp/internal/configs/version1/nginx$PLUS.tmpl \
/tmp/internal/configs/version2/nginx$PLUS.virtualserver.tmpl \
/tmp/internal/configs/version2/nginx$PLUS.transportserver.tmpl / \
&& if [ -z "${BUILD_OS##*plus*}" ]; then cp -a /tmp/internal/configs/version2/oidc.tmpl /; fi \
&& chown -R 101:0 /*.tmpl \
&& chmod -R g=u /*.tmpl \
&& setcap 'cap_net_bind_service=+ep' /nginx-ingress && setcap -v 'cap_net_bind_service=+ep' /nginx-ingress
Expand Down
3 changes: 2 additions & 1 deletion build/scripts/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ set -e

PLUS=""
if [ -z "${BUILD_OS##*plus*}" ]; then
mkdir -p /etc/nginx/oidc/
mkdir -p /etc/nginx/oidc/ /etc/nginx/oidc-conf.d/
cp -a /code/internal/configs/oidc/* /etc/nginx/oidc/
mkdir -p /etc/nginx/state_files/
mkdir -p /etc/nginx/reporting/
mkdir -p /etc/nginx/secrets/mgmt/
PLUS=-plus
cp -a /code/internal/configs/version2/oidc.tmpl /
fi

mkdir -p /etc/nginx/njs/ && cp -a /code/internal/configs/njs/* /etc/nginx/njs/
Expand Down
12 changes: 11 additions & 1 deletion cmd/nginx-ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const (
socketPath = "/var/lib/nginx"
fatalEventFlushTime = 200 * time.Millisecond
secretErrorReason = "SecretError"
fileErrorReason = "FileError"
configMapErrorReason = "ConfigMapError"
)

Expand Down Expand Up @@ -191,6 +192,12 @@ func main() {
if err != nil {
logEventAndExit(ctx, eventRecorder, pod, secretErrorReason, err)
}

caBundlePath, err := nginxManager.GetOSCABundlePath()
if err != nil {
logEventAndExit(ctx, eventRecorder, pod, fileErrorReason, err)
}

globalConfigurationValidator := createGlobalConfigurationValidator()

mustProcessGlobalConfiguration(ctx)
Expand Down Expand Up @@ -226,6 +233,7 @@ func main() {
StaticSSLPath: staticSSLPath,
NginxVersion: nginxVersion,
AppProtectBundlePath: appProtectBundlePath,
DefaultCABundle: caBundlePath,
}

if *nginxPlus {
Expand Down Expand Up @@ -541,11 +549,13 @@ func createTemplateExecutors(ctx context.Context) (*version1.TemplateExecutor, *
nginxIngressTemplatePath := "nginx.ingress.tmpl"
nginxVirtualServerTemplatePath := "nginx.virtualserver.tmpl"
nginxTransportServerTemplatePath := "nginx.transportserver.tmpl"
nginxOIDCConfTemplatePath := ""
if *nginxPlus {
nginxConfTemplatePath = "nginx-plus.tmpl"
nginxIngressTemplatePath = "nginx-plus.ingress.tmpl"
nginxVirtualServerTemplatePath = "nginx-plus.virtualserver.tmpl"
nginxTransportServerTemplatePath = "nginx-plus.transportserver.tmpl"
nginxOIDCConfTemplatePath = "oidc.tmpl"
}

if *mainTemplatePath != "" {
Expand All @@ -566,7 +576,7 @@ func createTemplateExecutors(ctx context.Context) (*version1.TemplateExecutor, *
nl.Fatalf(l, "Error creating TemplateExecutor: %v", err)
}

templateExecutorV2, err := version2.NewTemplateExecutor(nginxVirtualServerTemplatePath, nginxTransportServerTemplatePath)
templateExecutorV2, err := version2.NewTemplateExecutor(nginxVirtualServerTemplatePath, nginxTransportServerTemplatePath, nginxOIDCConfTemplatePath)
if err != nil {
nl.Fatalf(l, "Error creating TemplateExecutorV2: %v", err)
}
Expand Down
19 changes: 19 additions & 0 deletions config/crd/bases/k8s.nginx.org_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,29 @@ spec:
with a + sign, for example openid+profile+email, openid+email+userDefinedScope.
The default is openid.
type: string
sslVerify:
default: false
description: Enables verification of the IDP server SSL certificate.
Default is false.
type: boolean
sslVerifyDepth:
default: 1
description: Sets the verification depth in the IDP server certificates
chain. The default is 1.
minimum: 0
type: integer
tokenEndpoint:
description: URL for the token endpoint provided by your OpenID
Connect provider.
type: string
trustedCertSecret:
description: The name of the Kubernetes secret that stores the
CA certificate for IDP server verification. It must be in the
same namespace as the Policy resource. The secret must be of
the type nginx.org/ca, and the certificate must be stored in
the secret under the key ca.crt.
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
zoneSyncLeeway:
description: Specifies the maximum timeout in milliseconds for
synchronizing ID/access tokens and shared values between Ingress
Expand Down
19 changes: 19 additions & 0 deletions deploy/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -561,10 +561,29 @@ spec:
with a + sign, for example openid+profile+email, openid+email+userDefinedScope.
The default is openid.
type: string
sslVerify:
default: false
description: Enables verification of the IDP server SSL certificate.
Default is false.
type: boolean
sslVerifyDepth:
default: 1
description: Sets the verification depth in the IDP server certificates
chain. The default is 1.
minimum: 0
type: integer
tokenEndpoint:
description: URL for the token endpoint provided by your OpenID
Connect provider.
type: string
trustedCertSecret:
description: The name of the Kubernetes secret that stores the
CA certificate for IDP server verification. It must be in the
same namespace as the Policy resource. The secret must be of
the type nginx.org/ca, and the certificate must be stored in
the secret under the key ca.crt.
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
zoneSyncLeeway:
description: Specifies the maximum timeout in milliseconds for
synchronizing ID/access tokens and shared values between Ingress
Expand Down
3 changes: 3 additions & 0 deletions docs/crd/k8s.nginx.org_policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ The `.spec` object supports the following fields:
| `oidc.postLogoutRedirectURI` | `string` | URI to redirect to after the logout has been performed. Requires endSessionEndpoint. The default is /_logout. |
| `oidc.redirectURI` | `string` | Allows overriding the default redirect URI. The default is /_codexch. |
| `oidc.scope` | `string` | List of OpenID Connect scopes. The scope openid always needs to be present and others can be added concatenating them with a + sign, for example openid+profile+email, openid+email+userDefinedScope. The default is openid. |
| `oidc.sslVerify` | `boolean` | Enables verification of the IDP server SSL certificate. Default is false. |
| `oidc.sslVerifyDepth` | `integer` | Sets the verification depth in the IDP server certificates chain. The default is 1. |
| `oidc.tokenEndpoint` | `string` | URL for the token endpoint provided by your OpenID Connect provider. |
| `oidc.trustedCertSecret` | `string` | The name of the Kubernetes secret that stores the CA certificate for IDP server verification. It must be in the same namespace as the Policy resource. The secret must be of the type nginx.org/ca, and the certificate must be stored in the secret under the key ca.crt. |
| `oidc.zoneSyncLeeway` | `integer` | Specifies the maximum timeout in milliseconds for synchronizing ID/access tokens and shared values between Ingress Controller pods. The default is 200. |
| `rateLimit` | `object` | The rate limit policy controls the rate of processing requests per a defined key. |
| `rateLimit.burst` | `integer` | Excessive requests are delayed until their number exceeds the burst size, in which case the request is terminated with an error. |
Expand Down
7 changes: 7 additions & 0 deletions examples/common-secrets/keycloak-ca-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: keycloak-ca
type: nginx.org/ca
data:
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ4ekNDQTYrZ0F3SUJBZ0lVSnIxb2VDQTcxTmhjQ3VIVmh1NHVQcXNEVDhjd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2N6RUxNQWtHQTFVRUJoTUNTVVV4RFRBTEJnTlZCQWdNQkVOdmNtc3hEVEFMQmdOVkJBY01CRU52Y21zeApDekFKQmdOVkJBb01Ba1kxTVF3d0NnWURWUVFMREFOT1NVTXhLekFwQmdOVkJBTU1JbXRsZVdOc2IyRnJMbVJsClptRjFiSFF1YzNaakxtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qVXhNVEUzTVRJeU9EVTVXaGNOTXpVeE1URTEKTVRJeU9EVTVXakJ6TVFzd0NRWURWUVFHRXdKSlJURU5NQXNHQTFVRUNBd0VRMjl5YXpFTk1Bc0dBMVVFQnd3RQpRMjl5YXpFTE1Ba0dBMVVFQ2d3Q1JqVXhEREFLQmdOVkJBc01BMDVKUXpFck1Da0dBMVVFQXd3aWEyVjVZMnh2CllXc3VaR1ZtWVhWc2RDNXpkbU11WTJ4MWMzUmxjaTVzYjJOaGJEQ0NBaUl3RFFZSktvWklodmNOQVFFQkJRQUQKZ2dJUEFEQ0NBZ29DZ2dJQkFLK1M5cVRXdGZ3YU1GdjI3aU5Ob3FJU2FaMXJ4R0VlZlpTd29ZTCtBZEVpTXhEMgpxN0dvbkc3QlZaOVoxQWpMTlhtcGwyeFF1ZVZBN25RYXpXa2JJazhhQytJOWwwQ1NudXNiK1UwMUV6V0g5MVJ2CnNRM0pFdlV1WXlma3loNnRGeGoyNTJFMTQ3TE1HcTlXOHVlRDNJYnNWVjRZWlBDY0VHN1c3aEYzSTVObllYa1kKYzVKWFFtKzNTODl5V2hWUDg1MHpGTEpTVUFkcHhuMW9qdmFhV3FZWHVrODd4ajU3U1VueGpzR3pTN1dxMDJxVAo0WjVBNENjUGR5Y2Vha2MzcDRLQXVQaHZSYnltK2l1ME13WWFDZ3ZDZnV2eTZLa0l5c1lCL0dUUmhKcFltU2FKCmNsUXFxT3NRTjd6U3crZDlrOFNlSnFONGVCMHVFbmNkSkRwcDJTa05qa3ZRaEhkRllncm5KN1dZVmZBSmxibkIKSUc3VVBmRCtIZDduRkhFdWIvdE4zN20xRnkyZjhjUnNkNWZ1Q29NNHhoelRucm1wbDFiTEZYN2dOLzgrNGZmZwpzc2VCSmZqbExKbCtzTTA4RlI0aVFicGMzK0xZbkpzOXdsUE82VTBzQ2VJUXB4SnVTUEh5aENtN2hFR2N5NGdKCi9SdENwZHVabitrMDdnTXVnUlVVdUxNV2JNWjhaTEh2cWNwbTliY05aQXNxRERzdkIxMTdldFh4Q1luVC9DRGMKYVJBMVh3a2ptNS9DY0k0U0JqNzQrZC8rMnJDbHBEZGl0dWxyTnJ6WjcrVTZFSU1pdDV3SnJ0V09nVlpYdGNxOAp3UlFWdHZINVNkZjZnWVlvOE9HejhRenAzZEJ5TEVaWDU1V0FzT084dWhhcUJGenk1TEVaSlk1WEo2SlpBZ01CCkFBR2pVekJSTUIwR0ExVWREZ1FXQkJUcmR5VTZzRVhRWHR5S1doRFhVbHJaMmpjQXhEQWZCZ05WSFNNRUdEQVcKZ0JUcmR5VTZzRVhRWHR5S1doRFhVbHJaMmpjQXhEQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEdDU3FHU0liMwpEUUVCQ3dVQUE0SUNBUUNTTDNFcDBrWUFBUnIrSStVcjhRdmhkdDg1NUtIbFIwenMyZkN3elFORjArUlFXTm1WCjF5ZitUaEkwRW13NnVaWk5rYXNWenFQcENZNU81VWkwK2ZhL09LaUs1eGdXVjVlais1SEJndFk1VitVVE9yYm0KTlpiODE2UnBya1NadTBFWlpqNVNnUEhmSzUraVNuWnVKL0J2Nk13WjNYR2F1N3pHdlBBRGpqSVBwMEJqczluVwpWUXQyQWRmZlQrUXd1UHgreVJEZTFEdHhpSkEvMlQxN0c3eUN3NnBDWHJsVHdNckk5dTVtSHhmM0FSaVJOZ01mCndPSHJBNXNJYkhFVVQ5QXc3WXp6OFZMNHk0d29la3IzRmwxUjh2OVkzKzJaNmw2TWNIS3FGV2owVkxCU0JFdnAKZzFBSGtZcUdRL2NzbXBOSWMvQ2ZUdWFSdzVsTFZqNlpVMjNmaENsYW9ldWUyejk2U0w5NGltZnFlbUJNOGtwNQp3djNoS3dqdVBsMGxEbkxKd21IOHFMK094L0Y3eTZseWhnSnRVeGtsc0hWQXlPeDlrekM4ZkNZL3BMbDhqc1lvCnh0c29ZMlRhcW1uSEUwNk5KaUk4VVBLd3NzM1M1cUFEV2xzSk4rc2ZURzViVTFZWlVUcjhybi9yc2VlQ1dpOFkKTFdXV3JHeVBjeVd3ZDNJY0pZSVIyWEdXNHNDYkpUdHllMGszRm9WUHV3VHdSVGlkVnVaWjN4OXIzbkpuSk84WAphdkpXa1Z3b0paK1ZRd1AyN1BPc1RFZVR2cFNWMjZkNENJYlZmSnRDZldhd1cybUU5QlozZ1RBbmFuK2pEQTl4CndTektWSW0yYnBIZCtHU0QwUXJvZkNXL2h1OUN2Q2p4aGR0aHlSYzJKOWd2SzFXMjAwZW9HdFl6d0E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
8 changes: 8 additions & 0 deletions examples/common-secrets/keycloak-tls-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: keycloak-tls
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ4ekNDQTYrZ0F3SUJBZ0lVSnIxb2VDQTcxTmhjQ3VIVmh1NHVQcXNEVDhjd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2N6RUxNQWtHQTFVRUJoTUNTVVV4RFRBTEJnTlZCQWdNQkVOdmNtc3hEVEFMQmdOVkJBY01CRU52Y21zeApDekFKQmdOVkJBb01Ba1kxTVF3d0NnWURWUVFMREFOT1NVTXhLekFwQmdOVkJBTU1JbXRsZVdOc2IyRnJMbVJsClptRjFiSFF1YzNaakxtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qVXhNVEUzTVRJeU9EVTVXaGNOTXpVeE1URTEKTVRJeU9EVTVXakJ6TVFzd0NRWURWUVFHRXdKSlJURU5NQXNHQTFVRUNBd0VRMjl5YXpFTk1Bc0dBMVVFQnd3RQpRMjl5YXpFTE1Ba0dBMVVFQ2d3Q1JqVXhEREFLQmdOVkJBc01BMDVKUXpFck1Da0dBMVVFQXd3aWEyVjVZMnh2CllXc3VaR1ZtWVhWc2RDNXpkbU11WTJ4MWMzUmxjaTVzYjJOaGJEQ0NBaUl3RFFZSktvWklodmNOQVFFQkJRQUQKZ2dJUEFEQ0NBZ29DZ2dJQkFLK1M5cVRXdGZ3YU1GdjI3aU5Ob3FJU2FaMXJ4R0VlZlpTd29ZTCtBZEVpTXhEMgpxN0dvbkc3QlZaOVoxQWpMTlhtcGwyeFF1ZVZBN25RYXpXa2JJazhhQytJOWwwQ1NudXNiK1UwMUV6V0g5MVJ2CnNRM0pFdlV1WXlma3loNnRGeGoyNTJFMTQ3TE1HcTlXOHVlRDNJYnNWVjRZWlBDY0VHN1c3aEYzSTVObllYa1kKYzVKWFFtKzNTODl5V2hWUDg1MHpGTEpTVUFkcHhuMW9qdmFhV3FZWHVrODd4ajU3U1VueGpzR3pTN1dxMDJxVAo0WjVBNENjUGR5Y2Vha2MzcDRLQXVQaHZSYnltK2l1ME13WWFDZ3ZDZnV2eTZLa0l5c1lCL0dUUmhKcFltU2FKCmNsUXFxT3NRTjd6U3crZDlrOFNlSnFONGVCMHVFbmNkSkRwcDJTa05qa3ZRaEhkRllncm5KN1dZVmZBSmxibkIKSUc3VVBmRCtIZDduRkhFdWIvdE4zN20xRnkyZjhjUnNkNWZ1Q29NNHhoelRucm1wbDFiTEZYN2dOLzgrNGZmZwpzc2VCSmZqbExKbCtzTTA4RlI0aVFicGMzK0xZbkpzOXdsUE82VTBzQ2VJUXB4SnVTUEh5aENtN2hFR2N5NGdKCi9SdENwZHVabitrMDdnTXVnUlVVdUxNV2JNWjhaTEh2cWNwbTliY05aQXNxRERzdkIxMTdldFh4Q1luVC9DRGMKYVJBMVh3a2ptNS9DY0k0U0JqNzQrZC8rMnJDbHBEZGl0dWxyTnJ6WjcrVTZFSU1pdDV3SnJ0V09nVlpYdGNxOAp3UlFWdHZINVNkZjZnWVlvOE9HejhRenAzZEJ5TEVaWDU1V0FzT084dWhhcUJGenk1TEVaSlk1WEo2SlpBZ01CCkFBR2pVekJSTUIwR0ExVWREZ1FXQkJUcmR5VTZzRVhRWHR5S1doRFhVbHJaMmpjQXhEQWZCZ05WSFNNRUdEQVcKZ0JUcmR5VTZzRVhRWHR5S1doRFhVbHJaMmpjQXhEQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEdDU3FHU0liMwpEUUVCQ3dVQUE0SUNBUUNTTDNFcDBrWUFBUnIrSStVcjhRdmhkdDg1NUtIbFIwenMyZkN3elFORjArUlFXTm1WCjF5ZitUaEkwRW13NnVaWk5rYXNWenFQcENZNU81VWkwK2ZhL09LaUs1eGdXVjVlais1SEJndFk1VitVVE9yYm0KTlpiODE2UnBya1NadTBFWlpqNVNnUEhmSzUraVNuWnVKL0J2Nk13WjNYR2F1N3pHdlBBRGpqSVBwMEJqczluVwpWUXQyQWRmZlQrUXd1UHgreVJEZTFEdHhpSkEvMlQxN0c3eUN3NnBDWHJsVHdNckk5dTVtSHhmM0FSaVJOZ01mCndPSHJBNXNJYkhFVVQ5QXc3WXp6OFZMNHk0d29la3IzRmwxUjh2OVkzKzJaNmw2TWNIS3FGV2owVkxCU0JFdnAKZzFBSGtZcUdRL2NzbXBOSWMvQ2ZUdWFSdzVsTFZqNlpVMjNmaENsYW9ldWUyejk2U0w5NGltZnFlbUJNOGtwNQp3djNoS3dqdVBsMGxEbkxKd21IOHFMK094L0Y3eTZseWhnSnRVeGtsc0hWQXlPeDlrekM4ZkNZL3BMbDhqc1lvCnh0c29ZMlRhcW1uSEUwNk5KaUk4VVBLd3NzM1M1cUFEV2xzSk4rc2ZURzViVTFZWlVUcjhybi9yc2VlQ1dpOFkKTFdXV3JHeVBjeVd3ZDNJY0pZSVIyWEdXNHNDYkpUdHllMGszRm9WUHV3VHdSVGlkVnVaWjN4OXIzbkpuSk84WAphdkpXa1Z3b0paK1ZRd1AyN1BPc1RFZVR2cFNWMjZkNENJYlZmSnRDZldhd1cybUU5QlozZ1RBbmFuK2pEQTl4CndTektWSW0yYnBIZCtHU0QwUXJvZkNXL2h1OUN2Q2p4aGR0aHlSYzJKOWd2SzFXMjAwZW9HdFl6d0E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRZ0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1N3d2dna29BZ0VBQW9JQ0FRQ3ZrdmFrMXJYOEdqQmIKOXU0alRhS2lFbW1kYThSaEhuMlVzS0dDL2dIUklqTVE5cXV4cUp4dXdWV2ZXZFFJeXpWNXFaZHNVTG5sUU81MApHczFwR3lKUEdndmlQWmRBa3A3ckcvbE5OUk0xaC9kVWI3RU55UkwxTG1NbjVNb2VyUmNZOXVkaE5lT3l6QnF2ClZ2TG5nOXlHN0ZWZUdHVHduQkJ1MXU0UmR5T1RaMkY1R0hPU1YwSnZ0MHZQY2xvVlQvT2RNeFN5VWxBSGFjWjkKYUk3Mm1scW1GN3BQTzhZK2UwbEo4WTdCczB1MXF0TnFrK0dlUU9BbkQzY25IbXBITjZlQ2dMajRiMFc4cHZvcgp0RE1HR2dvTHduN3I4dWlwQ01yR0FmeGswWVNhV0prbWlYSlVLcWpyRURlODBzUG5mWlBFbmlhamVIZ2RMaEozCkhTUTZhZGtwRFk1TDBJUjNSV0lLNXllMW1GWHdDWlc1d1NCdTFEM3cvaDNlNXhSeExtLzdUZCs1dFJjdG4vSEUKYkhlWDdncURPTVljMDU2NXFaZFd5eFYrNERmL1B1SDM0TExIZ1NYNDVTeVpmckROUEJVZUlrRzZYTi9pMkp5YgpQY0pUenVsTkxBbmlFS2NTYmtqeDhvUXB1NFJCbk11SUNmMGJRcVhibVovcE5PNERMb0VWRkxpekZtekdmR1N4Cjc2bktadlczRFdRTEtndzdMd2RkZTNyVjhRbUowL3dnM0drUU5WOEpJNXVmd25DT0VnWSsrUG5mL3Rxd3BhUTMKWXJicGF6YTgyZS9sT2hDRElyZWNDYTdWam9GV1Y3WEt2TUVVRmJieCtVblgrb0dHS1BEaHMvRU02ZDNRY2l4RwpWK2VWZ0xEanZMb1dxZ1JjOHVTeEdTV09WeWVpV1FJREFRQUJBb0lDQUI4YzFtczRoekJBL2MvV0xyWC8xSEdPCi9MdENOUjhXdFo5TE81dklZazhLbGUwTUlUbk96TVhOcWR3ZW9YWGJlTUx4L0J6Y0kwME9XQk1vQ3IxMDZ2d0UKZkJXZjMzVTRaa1A0aFpHYWRhaDNTeXRoellqSldId3RON0lDbDVTZkRLaEdYSk03NXZrd3RRdmNSeGdpcEVvZQppRFF2ODNjMTJLMmpsYlZ2bk5US3JabTFiUW1DUUFvbSs1NnJ2MjNtYUorelJSZ2lnUDhIVGY2OE1CVmdIZTh2CjVqcVRONXFyNHoxZ3VuRDEwbFZEaThwbm9VUVhjQUZMK3N2cVZsLy9hMFl6aEZPMStEQXBrTXg4MXN2ZWdtZzYKRTU3QlFWeHU2K3Z4dnlXb2dTeU94Ymp3QTF3SjRUd2llQllVYlZYUXlZWStsazlDa2xwdFp5VkhlenVFdFZBRwoxSkdDb3NyZkl0eUE3YXdSVXFSMWFwajJPS0Jyd1dROW5nK0dvR0RRNnBLTHdzN1F6TUlkUVNMZGY2SVZMZWE4CjJTZ1AyK0hycUIrR1g5ZlJGdUFPRTRQNGprNGtMT3FTSkR4OENvdkdiS3NSUm1RR1lGL282dFhJYm5zZFVNaUsKZUVuYUVINXRmVWZ1WXNlWTlmR3pTUGFJT1FLeXI4TWJtanp3NFprNE9VUmxRWE41K3kzOGxXREVyb3NybG9VUwpZSkxucU5sVUEvNk96d3RTaCtRenFLeGEzcS9jYndFbU54NDYzOTlzRDNxSndXQlBXcng1REtiSlp4SWtQNFVOCnE1YUVGZW5kY09mZTNxNG9uQm1FQ28weW1zRko0eGNTUFdYUGJtRk91dHJiTnN5R0xDRURlUjNpRmM0Qk12aTYKZURwSHl0MTlXTDloLysyVC9OeG5Bb0lCQVFEd2JLaTd3TUgyWUlUYzBkTi82SEtiaVdJQXVFZkZMbzZSOUxuTwpaWnR2QW5tQTNSTmpJanB3R1BGQ3A0QjdjdEpiVWtIbzlSemdZQ3RKeGF6ZE5wWXRRaFhMRFZ0c0ExZ21zQUo0CmJwcVUrVnhoeTd5R1lzZDNZVDRVcUVRZFNtbHdYRHhuSFlsb0hWd3ExQlRxeWNZSjNYZGV6bE9BS0JzZFR6eGwKZmJGdjJjSXM3aTBUcU1VbzZSd1hVcnhSSU85TGNJL2ZaSWROVmd4MVk2Nm9SaXoxdnlVYVlISUNGM3V6OHl6VgpYc1JoMVZzNFdXVXRhTmlzbXBWTkFnTEtHTUthWGlKNU9pRjA1RWljcUs0bWtNR2lUMFFaZElpNkdyd3dEMkU5Cm5qZjY2bkwycmtGQXRudHpaVkdXMTVwVkUxc2ZJV0UrbHcycGlhSjFESTVNVm4zYkFvSUJBUUM2OHNtWTZFK2QKQmVzTUpuSXhVaXdPUWZ6a256OG5udGVvMzFSb2c5R1dDejBnNlJFVnBRdDZkVU43Wi8yQnZKVUtkNk0yZmlYYwppUmNFRTF6RHpaZ3RMVmhWbG5ZaWZnT1lsbU9NRlRZbHRGUnQ0emgzbUM1TUxOVThVVmhNUjE2cHYvVkF2RmhpCnlxbVNQVG1acjNVZkNKdjZjeFZRbFZJYlVTTVI3bmlSSW13T2JPYUJ3OFpCV0JaOUVCYi90N0ROUVpCV2ZKdTIKejdESXhsUXBxcFM1aWZLRlZpT2pCL0hXcnJ1RHpLUjROdFFDSkRoZERoUlZCWWxsSkpKRE9GV3VCbm5hS3EveQpiZWlrV2dSZmI3YUNSOU5LUVk3aHM4d2dDL0MzNFVBR2lyN2pJNURpQmFKOGVlM0xoMk5GRTI4VWFUTW5mSDNJClFQbVN1MUI2NjJqYkFvSUJBSEZ6QktnY0RDckRYczZJWUtIeHdPcnVDQVhJNzJ6M1RDVkpjc2dYSUNKZzY0N0kKUTFhN0Z4SkFZdEFPRkUyc1grRGh6dUlyajZXOUc1QWpMQy95aXlqdUR6U1NwL292RmRDanEzYkMwa1RMNmpEbgpuNTFXVFVOaTZwVjYxVEZ4SkpIMXBEY1FNLytpSXhTK29PUXR0RHFCZTh1TDF0RVptN25YNHVzTlJjWSszaWF2CmVTdldycnBnVFhZZi8yYlZBTFg3ZHBoMmFuWXV6WkF6S242VEpySUxzV2xoNjBwYlpHOEVwN3BEanEyUHJRekkKK2pwVVNESWllNk1yK0w3K3NnMS9zQXErU0gxTkg0cDAra0NPZkNDb0FMMTJSUEowblNxY2gwazVPTGM1SEdpVQp6NHZHMERnaXJqNWNuS0hha1Z2K04xSCttMTdONkpBTkRiU3Q5NU1DZ2dFQkFLS3R4UG4zSmRoSkh4bEtsMUlOCjVHSmZ6N1lPVVVHaitweHNBcUtVR3B4TG1WejdFeS9YbUI1dXpsTWowYmpFcHBrZU5IdWwyRUtKVk9ycUFtNHMKaVFDL0ZjQWNseDQ2czl4aSthc2JoaXZYT1NVS2RjZTBPSTEyOGZOMEFiY1czK3d0S3ppeTdPTEM0ajVzWXFRMgp4MTlDK2FBOTVzMWhzcm9zcDZ6aDdDNjNXbnBQRDJMYVBybjc4azNQNDRPUWtCeDhzaUpnZW92aFBUL3BQYkdvClM1VU0wbXB1NDhIcGx1dXV6MlBJZjFKUXU3cEZWSHE5VnJvSmdGN3dMUXFyaWZ0T2pWaG9qd1VSMlVDelNGelgKOUdSNEpnZlc5b08zRnFqSVd5ZFhyb1JDMWdzSGx2cm4xbFlsTCtWTklmZ3BDaDhqMEN6TEt4VklYU1R2TlFCUgp1OE1DZ2dFQWJuQ0JSdmtNUnh6S1NkM1V5USszV0tBdnYzVHk2L1phNy8rVVBhZzFRTE5RSksram50L3AvNWpzCkRCRWhPSDhMT2MxTnEzSEpWUVBjV2kwVXo1aDhOWVJwUWx2QWNlRjRTb3d4ZGIrWWRTS1RIN3daVDFwa1dkY0kKWTNib1N0Y0hsOVlCM1Q5STVIZWR4dHgzMytNSkppMXcwVGswK3lNcHBJUmV2VFg3WjVpbnZpbHE3eWJITzd6NwoxbHRHaGJJTjZSblVzcy9mZE1ianJ4N3dreEtqMmlKLzYyM3B1Z0tsakdndUlVR1l3ODNaeUREaGp3ZVJleTlMCmphQVViN01CQ2xsR0NNS3hEZGtsUUN3eHh0YmpySk1QZ0JSbVRjK093QzNwUDNqemJLNlRuQWhnL3JxeEpaYnAKN3hFL0lyd3lxOXJtY3lqZzlsVkh0RHRFN1RDSVRnPT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=
Loading
Loading