diff --git a/Dockerfile.aro-e2e b/Dockerfile.aro-e2e index b6e29f87f88..47ca923efb4 100644 --- a/Dockerfile.aro-e2e +++ b/Dockerfile.aro-e2e @@ -11,7 +11,7 @@ RUN make aro RELEASE=${IS_OFFICIAL_RELEASE} -o generate && make e2e.test e2etool FROM ${REGISTRY}/ubi8/ubi-minimal RUN microdnf update && microdnf clean all -COPY --from=builder /go/src/github.com/Azure/ARO-RP/aro /go/src/github.com/Azure/ARO-RP/e2e.test /go/src/github.com/Azure/ARO-RP/db /go/src/github.com/Azure/ARO-RP/cluster /go/src/github.com/Azure/ARO-RP/portalauth /go/src/github.com/Azure/ARO-RP/jq /usr/local/bin/ +COPY --from=builder /go/src/github.com/Azure/ARO-RP/aro /go/src/github.com/Azure/ARO-RP/e2e.test /go/src/github.com/Azure/ARO-RP/db /go/src/github.com/Azure/ARO-RP/cluster /go/src/github.com/Azure/ARO-RP/jq /usr/local/bin/ ENTRYPOINT ["aro"] EXPOSE 2222/tcp 8080/tcp 8443/tcp 8444/tcp 8445/tcp USER 1000 diff --git a/Makefile b/Makefile index f62467ed10c..6f84395e992 100644 --- a/Makefile +++ b/Makefile @@ -187,7 +187,6 @@ e2e.test: e2etools: CGO_ENABLED=0 go build -ldflags "-X github.com/Azure/ARO-RP/pkg/util/version.GitCommit=$(VERSION)" ./hack/cluster CGO_ENABLED=0 go build -ldflags "-X github.com/Azure/ARO-RP/pkg/util/version.GitCommit=$(VERSION)" ./hack/db - CGO_ENABLED=0 go build -ldflags "-X github.com/Azure/ARO-RP/pkg/util/version.GitCommit=$(VERSION)" ./hack/portalauth CGO_ENABLED=0 go build ./hack/jq test-e2e: e2e.test diff --git a/cmd/aro/portal.go b/cmd/aro/portal.go index dd251133cd9..af01a4af07e 100644 --- a/cmd/aro/portal.go +++ b/cmd/aro/portal.go @@ -10,6 +10,7 @@ import ( "os" "strings" + "github.com/coreos/go-oidc/v3/oidc" "github.com/sirupsen/logrus" "github.com/Azure/ARO-RP/pkg/database" @@ -20,7 +21,6 @@ import ( "github.com/Azure/ARO-RP/pkg/proxy" "github.com/Azure/ARO-RP/pkg/util/encryption" "github.com/Azure/ARO-RP/pkg/util/keyvault" - "github.com/Azure/ARO-RP/pkg/util/oidc" "github.com/Azure/ARO-RP/pkg/util/uuid" ) @@ -155,10 +155,11 @@ func portal(ctx context.Context, log *logrus.Entry, audit *logrus.Entry) error { } clientID := os.Getenv("AZURE_PORTAL_CLIENT_ID") - verifier, err := oidc.NewVerifier(ctx, _env.Environment().ActiveDirectoryEndpoint+_env.TenantID()+"/v2.0", clientID) + provider, err := oidc.NewProvider(ctx, _env.Environment().ActiveDirectoryEndpoint+_env.TenantID()+"/v2.0") if err != nil { return err } + verifier := provider.Verifier(&oidc.Config{ClientID: clientID}) // In development the portal API is proxied by the frontend dev server which is // hosted at localhost:3000, so the hostname needs to be set to that. diff --git a/go.mod b/go.mod index dd0fd687470..764a5a1d67e 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/codahale/etm v0.0.0-20141003032925-c00c9e6fb4c9 github.com/containers/image/v5 v5.22.0 github.com/containers/podman/v4 v4.1.1 - github.com/coreos/go-oidc v2.2.1+incompatible + github.com/coreos/go-oidc/v3 v3.6.0 github.com/coreos/go-semver v0.3.0 github.com/coreos/go-systemd/v22 v22.3.2 github.com/coreos/ignition v0.35.0 @@ -29,14 +29,13 @@ require ( github.com/go-logr/logr v1.2.4 github.com/go-test/deep v1.0.8 github.com/gofrs/uuid v4.2.0+incompatible + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.5.9 github.com/googleapis/gnostic v0.6.8 github.com/gorilla/csrf v1.7.1 github.com/gorilla/mux v1.8.0 - github.com/gorilla/securecookie v1.1.1 - github.com/gorilla/sessions v1.2.1 github.com/itchyny/gojq v0.12.13 github.com/jewzaam/go-cosmosdb v0.0.0-20220315232836-282b67c5b234 github.com/jongio/azidext/go/azidext v0.4.0 @@ -65,7 +64,7 @@ require ( github.com/vincent-petithory/dataurl v1.0.0 golang.org/x/crypto v0.9.0 golang.org/x/net v0.10.0 - golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 + golang.org/x/oauth2 v0.6.0 golang.org/x/sync v0.1.0 golang.org/x/text v0.9.0 golang.org/x/tools v0.6.0 @@ -130,6 +129,7 @@ require ( github.com/fatih/color v1.14.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-errors/errors v1.4.2 // indirect + github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect @@ -149,6 +149,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/schema v1.2.0 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -199,7 +200,6 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/proglottis/gpgme v0.1.3 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect @@ -247,7 +247,7 @@ require ( k8s.io/apiserver v0.24.7 // indirect k8s.io/component-base v0.25.0 // indirect k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 // indirect - k8s.io/klog/v2 v2.70.1 // indirect + k8s.io/klog/v2 v2.80.1 // indirect k8s.io/kube-aggregator v0.24.1 // indirect k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect diff --git a/go.sum b/go.sum index 7d6416bf703..5ef130ef3dd 100644 --- a/go.sum +++ b/go.sum @@ -347,8 +347,8 @@ github.com/coredns/corefile-migration v1.0.14/go.mod h1:XnhgULOEouimnzgn0t4WPuFD github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= -github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o= +github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -545,6 +545,8 @@ github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= @@ -632,6 +634,8 @@ github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -771,8 +775,6 @@ github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -1340,8 +1342,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= -github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/proglottis/gpgme v0.1.1/go.mod h1:fPbW/EZ0LvwQtH8Hy7eixhp1eF3G39dtx7GUN+0Gmy0= github.com/proglottis/gpgme v0.1.3 h1:Crxx0oz4LKB3QXc5Ea0J19K/3ICfy3ftr5exgUK1AU0= github.com/proglottis/gpgme v0.1.3/go.mod h1:fPbW/EZ0LvwQtH8Hy7eixhp1eF3G39dtx7GUN+0Gmy0= @@ -1734,6 +1734,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200124225646-8b5121be2f68/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1856,8 +1857,8 @@ golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 h1:+jnHzr9VPj32ykQVai5DNahi9+NSp7yYuCsl5eAQtL0= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= 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= @@ -2271,8 +2272,8 @@ k8s.io/cri-api v0.23.0/go.mod h1:2edENu3/mkyW3c6fVPPPaVGEFbLRacJizBbSp7ZOLOo= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 h1:TT1WdmqqXareKxZ/oNXEUSwKlLiHzPMyB0t8BaFeBYI= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-aggregator v0.23.0 h1:IjY8CfGHH9WUvJXIaAsAxTzHDsaLVeaEqjkvo6MLMD0= k8s.io/kube-aggregator v0.23.0/go.mod h1:b1vpoaTWKZjCzvbe1KXFw3vPbISrghJsg7/RI8oZUME= k8s.io/kube-controller-manager v0.23.0/go.mod h1:iHapRJJBe+fWu6hG3ye43YMFEeZcnIlRxDUS72bwJoE= diff --git a/hack/portalauth/portalauth.go b/hack/portalauth/portalauth.go deleted file mode 100644 index 232fd512494..00000000000 --- a/hack/portalauth/portalauth.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -// Copyright (c) Microsoft Corporation. -// Licensed under the Apache License 2.0. - -import ( - "context" - "flag" - "fmt" - "net/http" - "os" - "strings" - "time" - - "github.com/gorilla/securecookie" - "github.com/gorilla/sessions" - "github.com/sirupsen/logrus" - - "github.com/Azure/ARO-RP/pkg/env" - "github.com/Azure/ARO-RP/pkg/util/keyvault" - utillog "github.com/Azure/ARO-RP/pkg/util/log" -) - -const ( - SessionName = "session" - SessionKeyExpires = "expires" - SessionKeyUsername = "user_name" - SessionKeyGroups = "groups" - KeyVaultPrefix = "KEYVAULT_PREFIX" -) - -func run(ctx context.Context, log *logrus.Entry) error { - username := flag.String("username", "testuser", "username of the portal user") - groups := flag.String("groups", "", "comma-separated list of groups the user is in") - - flag.Parse() - - _env, err := env.NewCore(ctx, log) - if err != nil { - return err - } - - msiKVAuthorizer, err := _env.NewMSIAuthorizer(env.MSIContextRP, _env.Environment().KeyVaultScope) - if err != nil { - return err - } - - if err := env.ValidateVars(KeyVaultPrefix); err != nil { - return err - } - keyVaultPrefix := os.Getenv(KeyVaultPrefix) - portalKeyvaultURI := keyvault.URI(_env, env.PortalKeyvaultSuffix, keyVaultPrefix) - portalKeyvault := keyvault.NewManager(msiKVAuthorizer, portalKeyvaultURI) - - sessionKey, err := portalKeyvault.GetBase64Secret(ctx, env.PortalServerSessionKeySecretName, "") - if err != nil { - return err - } - - store := sessions.NewCookieStore(sessionKey) - - store.MaxAge(0) - store.Options.Secure = true - store.Options.HttpOnly = true - store.Options.SameSite = http.SameSiteLaxMode - - session := sessions.NewSession(store, SessionName) - opts := *store.Options - session.Options = &opts - - session.Values[SessionKeyUsername] = username - session.Values[SessionKeyGroups] = strings.Split(*groups, ",") - session.Values[SessionKeyExpires] = time.Now().Add(time.Hour).Unix() - - encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, - store.Codecs...) - if err != nil { - return err - } - - // Print session variable to stdout - fmt.Printf("%s", encoded) - - return nil -} - -func main() { - log := utillog.GetLogger() - - if err := run(context.Background(), log); err != nil { - log.Fatal(err) - } -} diff --git a/pkg/deploy/assets/rp-production-parameters.json b/pkg/deploy/assets/rp-production-parameters.json index eb5855604f7..cd8e104b900 100644 --- a/pkg/deploy/assets/rp-production-parameters.json +++ b/pkg/deploy/assets/rp-production-parameters.json @@ -75,6 +75,9 @@ "disableCosmosDBFirewall": { "value": false }, + "disableOauth": { + "value": "false" + }, "fluentbitImage": { "value": "" }, diff --git a/pkg/deploy/assets/rp-production.json b/pkg/deploy/assets/rp-production.json index 67ecfc5556c..1d73669093d 100644 --- a/pkg/deploy/assets/rp-production.json +++ b/pkg/deploy/assets/rp-production.json @@ -98,6 +98,10 @@ "type": "bool", "defaultValue": false }, + "disableOauth": { + "type": "string", + "defaultValue": "false" + }, "fluentbitImage": { "type": "string" }, @@ -516,7 +520,7 @@ "autoUpgradeMinorVersion": true, "settings": {}, "protectedSettings": { - "script": "[base64(concat(base64ToString('c2V0IC1leAoK'),'ACRRESOURCEID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('acrResourceId')),''')\n','ADMINAPICLIENTCERTCOMMONNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('adminApiClientCertCommonName')),''')\n','ARMAPICLIENTCERTCOMMONNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('armApiClientCertCommonName')),''')\n','ARMCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('armClientId')),''')\n','AZURECLOUDNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureCloudName')),''')\n','AZURESECPACKQUALYSURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureSecPackQualysUrl')),''')\n','AZURESECPACKVSATENANTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureSecPackVSATenantId')),''')\n','BILLINGE2ESTORAGEACCOUNTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('billingE2EStorageAccountId')),''')\n','CLUSTERMDMACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdmAccount')),''')\n','CLUSTERMDSDACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdAccount')),''')\n','CLUSTERMDSDCONFIGVERSION=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdConfigVersion')),''')\n','CLUSTERMDSDNAMESPACE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdNamespace')),''')\n','CLUSTERPARENTDOMAINNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterParentDomainName')),''')\n','DATABASEACCOUNTNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('databaseAccountName')),''')\n','DBTOKENCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('dbtokenClientId')),''')\n','FLUENTBITIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fluentbitImage')),''')\n','FPCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fpClientId')),''')\n','FPSERVICEPRINCIPALID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fpServicePrincipalId')),''')\n','GATEWAYDOMAINS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayDomains')),''')\n','GATEWAYRESOURCEGROUPNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayResourceGroupName')),''')\n','GATEWAYSERVICEPRINCIPALID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayServicePrincipalId')),''')\n','KEYVAULTDNSSUFFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultDNSSuffix')),''')\n','KEYVAULTPREFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultPrefix')),''')\n','MDMFRONTENDURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdmFrontendUrl')),''')\n','MDSDENVIRONMENT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdsdEnvironment')),''')\n','PORTALACCESSGROUPIDS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalAccessGroupIds')),''')\n','PORTALCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalClientId')),''')\n','PORTALELEVATEDGROUPIDS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalElevatedGroupIds')),''')\n','RPFEATURES=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpFeatures')),''')\n','RPIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpImage')),''')\n','RPMDMACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdmAccount')),''')\n','RPMDSDACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdAccount')),''')\n','RPMDSDCONFIGVERSION=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdConfigVersion')),''')\n','RPMDSDNAMESPACE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdNamespace')),''')\n','RPPARENTDOMAINNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpParentDomainName')),''')\n','CLUSTERSINSTALLVIAHIVE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clustersInstallViaHive')),''')\n','CLUSTERSADOPTBYHIVE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clustersAdoptByHive')),''')\n','CLUSTERDEFAULTINSTALLERPULLSPEC=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterDefaultInstallerPullspec')),''')\n','USECHECKACCESS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('useCheckAccess')),''')\n','ADMINAPICABUNDLE=''',parameters('adminApiCaBundle'),'''\n','ARMAPICABUNDLE=''',parameters('armApiCaBundle'),'''\n','MDMIMAGE=''/genevamdm:2.2023.609.2051-821f47-20230706t0953''\n','LOCATION=$(base64 -d \u003c\u003c\u003c''',base64(resourceGroup().location),''')\n','SUBSCRIPTIONID=$(base64 -d \u003c\u003c\u003c''',base64(subscription().subscriptionId),''')\n','RESOURCEGROUPNAME=$(base64 -d \u003c\u003c\u003c''',base64(resourceGroup().name),''')\n','\n',base64ToString('IyEvYmluL2Jhc2gKCmVjaG8gInNldHRpbmcgc3NoIHBhc3N3b3JkIGF1dGhlbnRpY2F0aW9uIgojIFdlIG5lZWQgdG8gbWFudWFsbHkgc2V0IFBhc3N3b3JkQXV0aGVudGljYXRpb24gdG8gdHJ1ZSBpbiBvcmRlciBmb3IgdGhlIFZNU1MgQWNjZXNzIEpJVCB0byB3b3JrCnNlZCAtaSAncy9QYXNzd29yZEF1dGhlbnRpY2F0aW9uIG5vL1Bhc3N3b3JkQXV0aGVudGljYXRpb24geWVzL2cnIC9ldGMvc3NoL3NzaGRfY29uZmlnCnN5c3RlbWN0bCByZWxvYWQgc3NoZC5zZXJ2aWNlCgplY2hvICJydW5uaW5nIFJIVUkgZml4Igp5dW0gdXBkYXRlIC15IC0tZGlzYWJsZXJlcG89JyonIC0tZW5hYmxlcmVwbz0ncmh1aS1taWNyb3NvZnQtYXp1cmUqJwoKZWNobyAicnVubmluZyB5dW0gdXBkYXRlIgp5dW0gLXkgLXggV0FMaW51eEFnZW50IC14IFdBTGludXhBZ2VudC11ZGV2IHVwZGF0ZSAtLWFsbG93ZXJhc2luZwoKZWNobyAiZXh0ZW5kaW5nIHBhcnRpdGlvbiB0YWJsZSIKIyBMaW51eCBibG9jayBkZXZpY2VzIGFyZSBpbmNvbnNpc3RlbnRseSBuYW1lZAojIGl0J3MgZGlmZmljdWx0IHRvIHRpZSB0aGUgbHZtIHB2IHRvIHRoZSBwaHlzaWNhbCBkaXNrIHVzaW5nIC9kZXYvZGlzayBmaWxlcywgd2hpY2ggaXMgd2h5IGx2cyBpcyB1c2VkIGhlcmUKcGh5c2ljYWxEaXNrPSIkKGx2cyAtbyBkZXZpY2VzIC1hIHwgaGVhZCAtbjIgfCB0YWlsIC1uMSB8IGN1dCAtZCAnICcgLWYgMyB8IGN1dCAtZCBcKCAtZiAxIHwgdHIgLWQgJ1s6ZGlnaXQ6XScpIgpncm93cGFydCAiJHBoeXNpY2FsRGlzayIgMgoKZWNobyAiZXh0ZW5kaW5nIGZpbGVzeXN0ZW1zIgpsdmV4dGVuZCAtbCArMjAlRlJFRSAvZGV2L3Jvb3R2Zy9yb290bHYKeGZzX2dyb3dmcyAvCgpsdmV4dGVuZCAtbCArMTAwJUZSRUUgL2Rldi9yb290dmcvdmFybHYKeGZzX2dyb3dmcyAvdmFyCgplY2hvICJpbXBvcnRpbmcgcnBtIHJlcG9zaXRvcmllcyIKcnBtIC0taW1wb3J0IGh0dHBzOi8vZGwuZmVkb3JhcHJvamVjdC5vcmcvcHViL2VwZWwvUlBNLUdQRy1LRVktRVBFTC04CnJwbSAtLWltcG9ydCBodHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20va2V5cy9taWNyb3NvZnQuYXNjCgpmb3IgYXR0ZW1wdCBpbiB7MS4uNX07IGRvCiAgeXVtIC15IGluc3RhbGwgaHR0cHM6Ly9kbC5mZWRvcmFwcm9qZWN0Lm9yZy9wdWIvZXBlbC9lcGVsLXJlbGVhc2UtbGF0ZXN0LTgubm9hcmNoLnJwbSAmJiBicmVhawogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDUgXV07IHRoZW4gc2xlZXAgMTA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgplY2hvICJjb25maWd1cmluZyBsb2dyb3RhdGUiCmNhdCA+L2V0Yy9sb2dyb3RhdGUuY29uZiA8PCdFT0YnCiMgc2VlICJtYW4gbG9ncm90YXRlIiBmb3IgZGV0YWlscwojIHJvdGF0ZSBsb2cgZmlsZXMgd2Vla2x5CndlZWtseQoKIyBrZWVwIDIgd2Vla3Mgd29ydGggb2YgYmFja2xvZ3MKcm90YXRlIDIKCiMgY3JlYXRlIG5ldyAoZW1wdHkpIGxvZyBmaWxlcyBhZnRlciByb3RhdGluZyBvbGQgb25lcwpjcmVhdGUKCiMgdXNlIGRhdGUgYXMgYSBzdWZmaXggb2YgdGhlIHJvdGF0ZWQgZmlsZQpkYXRlZXh0CgojIHVuY29tbWVudCB0aGlzIGlmIHlvdSB3YW50IHlvdXIgbG9nIGZpbGVzIGNvbXByZXNzZWQKY29tcHJlc3MKCiMgUlBNIHBhY2thZ2VzIGRyb3AgbG9nIHJvdGF0aW9uIGluZm9ybWF0aW9uIGludG8gdGhpcyBkaXJlY3RvcnkKaW5jbHVkZSAvZXRjL2xvZ3JvdGF0ZS5kCgojIG5vIHBhY2thZ2VzIG93biB3dG1wIGFuZCBidG1wIC0tIHdlJ2xsIHJvdGF0ZSB0aGVtIGhlcmUKL3Zhci9sb2cvd3RtcCB7CiAgICBtb250aGx5CiAgICBjcmVhdGUgMDY2NCByb290IHV0bXAKICAgICAgICBtaW5zaXplIDFNCiAgICByb3RhdGUgMQp9CgovdmFyL2xvZy9idG1wIHsKICAgIG1pc3NpbmdvawogICAgbW9udGhseQogICAgY3JlYXRlIDA2MDAgcm9vdCB1dG1wCiAgICByb3RhdGUgMQp9CkVPRgoKZWNobyAiY29uZmlndXJpbmcgeXVtIHJlcG9zaXRvcnkgYW5kIHJ1bm5pbmcgeXVtIHVwZGF0ZSIKY2F0ID4vZXRjL3l1bS5yZXBvcy5kL2F6dXJlLnJlcG8gPDwnRU9GJwpbYXp1cmUtY2xpXQpuYW1lPWF6dXJlLWNsaQpiYXNldXJsPWh0dHBzOi8vcGFja2FnZXMubWljcm9zb2Z0LmNvbS95dW1yZXBvcy9henVyZS1jbGkKZW5hYmxlZD15ZXMKZ3BnY2hlY2s9eWVzCgpbYXp1cmVjb3JlXQpuYW1lPWF6dXJlY29yZQpiYXNldXJsPWh0dHBzOi8vcGFja2FnZXMubWljcm9zb2Z0LmNvbS95dW1yZXBvcy9henVyZWNvcmUKZW5hYmxlZD15ZXMKZ3BnY2hlY2s9bm8KRU9GCgpzZW1hbmFnZSBmY29udGV4dCAtYSAtdCB2YXJfbG9nX3QgIi92YXIvbG9nL2pvdXJuYWwoLy4qKT8iCm1rZGlyIC1wIC92YXIvbG9nL2pvdXJuYWwKCmZvciBhdHRlbXB0IGluIHsxLi41fTsgZG8KeXVtIC15IGluc3RhbGwgY2xhbWF2IGF6c2VjLWNsYW1hdiBhenNlYy1tb25pdG9yIGF6dXJlLWNsaSBhenVyZS1tZHNkIGF6dXJlLXNlY3VyaXR5IHBvZG1hbiBwb2RtYW4tZG9ja2VyIG9wZW5zc2wtcGVybCBweXRob24zICYmIGJyZWFrCiAgIyBoYWNrIC0gd2UgYXJlIGluc3RhbGxpbmcgcHl0aG9uMyBvbiBob3N0cyBkdWUgdG8gYW4gaXNzdWUgd2l0aCBBenVyZSBMaW51eCBFeHRlbnNpb25zIGh0dHBzOi8vZ2l0aHViLmNvbS9BenVyZS9henVyZS1saW51eC1leHRlbnNpb25zL3B1bGwvMTUwNQogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDUgXV07IHRoZW4gc2xlZXAgMTA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgojIGh0dHBzOi8vYWNjZXNzLnJlZGhhdC5jb20vc2VjdXJpdHkvY3ZlL2N2ZS0yMDIwLTEzNDAxCmVjaG8gImFwcGx5aW5nIGZpcmV3YWxsIHJ1bGVzIgpjYXQgPi9ldGMvc3lzY3RsLmQvMDItZGlzYWJsZS1hY2NlcHQtcmEuY29uZiA8PCdFT0YnCm5ldC5pcHY2LmNvbmYuYWxsLmFjY2VwdF9yYT0wCkVPRgoKY2F0ID4vZXRjL3N5c2N0bC5kLzAxLWRpc2FibGUtY29yZS5jb25mIDw8J0VPRicKa2VybmVsLmNvcmVfcGF0dGVybiA9IHwvYmluL3RydWUKRU9GCnN5c2N0bCAtLXN5c3RlbQoKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9NDQzL3RjcCAtLXBlcm1hbmVudApmaXJld2FsbC1jbWQgLS1hZGQtcG9ydD00NDQvdGNwIC0tcGVybWFuZW50CmZpcmV3YWxsLWNtZCAtLWFkZC1wb3J0PTQ0NS90Y3AgLS1wZXJtYW5lbnQKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9MjIyMi90Y3AgLS1wZXJtYW5lbnQKCmV4cG9ydCBBWlVSRV9DTE9VRF9OQU1FPSRBWlVSRUNMT1VETkFNRQoKZWNobyAibG9nZ2luZyBpbnRvIHByb2QgYWNyIgpheiBsb2dpbiAtaSAtLWFsbG93LW5vLXN1YnNjcmlwdGlvbnMKCiMgU3VwcHJlc3MgZW11bGF0aW9uIG91dHB1dCBmb3IgcG9kbWFuIGluc3RlYWQgb2YgZG9ja2VyIGZvciBheiBhY3IgY29tcGF0YWJpbGl0eQpta2RpciAtcCAvZXRjL2NvbnRhaW5lcnMvCnRvdWNoIC9ldGMvY29udGFpbmVycy9ub2RvY2tlcgoKbWtkaXIgLXAgL3Jvb3QvLmRvY2tlcgpSRUdJU1RSWV9BVVRIX0ZJTEU9L3Jvb3QvLmRvY2tlci9jb25maWcuanNvbiBheiBhY3IgbG9naW4gLS1uYW1lICIkKHNlZCAtZSAnc3wuKi98fCcgPDw8IiRBQ1JSRVNPVVJDRUlEIikiCgpNRE1JTUFHRT0iJHtSUElNQUdFJSUvKn0vJHtNRE1JTUFHRSMjKi99Igpkb2NrZXIgcHVsbCAiJE1ETUlNQUdFIgpkb2NrZXIgcHVsbCAiJFJQSU1BR0UiCmRvY2tlciBwdWxsICIkRkxVRU5UQklUSU1BR0UiCgpheiBsb2dvdXQKCmVjaG8gImNvbmZpZ3VyaW5nIGZsdWVudGJpdCBzZXJ2aWNlIgpta2RpciAtcCAvZXRjL2ZsdWVudGJpdC8KbWtkaXIgLXAgL3Zhci9saWIvZmx1ZW50CgpjYXQgPi9ldGMvZmx1ZW50Yml0L2ZsdWVudGJpdC5jb25mIDw8J0VPRicKW0lOUFVUXQoJTmFtZSBzeXN0ZW1kCglUYWcgam91cm5hbGQKCVN5c3RlbWRfRmlsdGVyIF9DT01NPWFybwoJREIgL3Zhci9saWIvZmx1ZW50L2pvdXJuYWxkYgoKW0ZJTFRFUl0KCU5hbWUgbW9kaWZ5CglNYXRjaCBqb3VybmFsZAoJUmVtb3ZlX3dpbGRjYXJkIF8KCVJlbW92ZSBUSU1FU1RBTVAKCltGSUxURVJdCglOYW1lIHJld3JpdGVfdGFnCglNYXRjaCBqb3VybmFsZAoJUnVsZSAkTE9HS0lORCBhc3luY3FvcyBhc3luY3FvcyB0cnVlCgpbRklMVEVSXQoJTmFtZSBtb2RpZnkKCU1hdGNoIGFzeW5jcW9zCglSZW1vdmUgQ0xJRU5UX1BSSU5DSVBBTF9OQU1FCglSZW1vdmUgRklMRQoJUmVtb3ZlIENPTVBPTkVOVAoKW0ZJTFRFUl0KCU5hbWUgcmV3cml0ZV90YWcKCU1hdGNoIGpvdXJuYWxkCglSdWxlICRMT0dLSU5EIGlmeGF1ZGl0IGlmeGF1ZGl0IGZhbHNlCgpbT1VUUFVUXQoJTmFtZSBmb3J3YXJkCglNYXRjaCAqCglQb3J0IDI5MjMwCkVPRgoKZWNobyAiRkxVRU5UQklUSU1BR0U9JEZMVUVOVEJJVElNQUdFIiA+L2V0Yy9zeXNjb25maWcvZmx1ZW50Yml0CgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZmx1ZW50Yml0LnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldApTdGFydExpbWl0SW50ZXJ2YWxTZWM9MAoKW1NlcnZpY2VdClJlc3RhcnRTZWM9MXMKRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2ZsdWVudGJpdApFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1zZWN1cml0eS1vcHQgbGFiZWw9ZGlzYWJsZSBcCiAgLS1lbnRyeXBvaW50IC9vcHQvdGQtYWdlbnQtYml0L2Jpbi90ZC1hZ2VudC1iaXQgXAogIC0tbmV0PWhvc3QgXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtdiAvZXRjL2ZsdWVudGJpdC9mbHVlbnRiaXQuY29uZjovZXRjL2ZsdWVudGJpdC9mbHVlbnRiaXQuY29uZiBcCiAgLXYgL3Zhci9saWIvZmx1ZW50Oi92YXIvbGliL2ZsdWVudDp6IFwKICAtdiAvdmFyL2xvZy9qb3VybmFsOi92YXIvbG9nL2pvdXJuYWw6cm8gXAogIC12IC9ldGMvbWFjaGluZS1pZDovZXRjL21hY2hpbmUtaWQ6cm8gXAogICRGTFVFTlRCSVRJTUFHRSBcCiAgLWMgL2V0Yy9mbHVlbnRiaXQvZmx1ZW50Yml0LmNvbmYKCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wICVOClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9NQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKbWtkaXIgL2V0Yy9hcm8tcnAKYmFzZTY0IC1kIDw8PCIkQURNSU5BUElDQUJVTkRMRSIgPi9ldGMvYXJvLXJwL2FkbWluLWNhLWJ1bmRsZS5wZW0KaWYgW1sgLW4gIiRBUk1BUElDQUJVTkRMRSIgXV07IHRoZW4KICBiYXNlNjQgLWQgPDw8IiRBUk1BUElDQUJVTkRMRSIgPi9ldGMvYXJvLXJwL2FybS1jYS1idW5kbGUucGVtCmZpCmNob3duIC1SIDEwMDA6MTAwMCAvZXRjL2Fyby1ycAoKZWNobyAiY29uZmlndXJpbmcgbWRtIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvbWRtIDw8RU9GCk1ETUZST05URU5EVVJMPSckTURNRlJPTlRFTkRVUkwnCk1ETUlNQUdFPSckTURNSU1BR0UnCk1ETVNPVVJDRUVOVklST05NRU5UPSckTE9DQVRJT04nCk1ETVNPVVJDRVJPTEU9cnAKTURNU09VUkNFUk9MRUlOU1RBTkNFPSckKGhvc3RuYW1lKScKRU9GCgpta2RpciAvdmFyL2V0dwpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vbWRtLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9tZG0KRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0tZW50cnlwb2ludCAvdXNyL3NiaW4vTWV0cmljc0V4dGVuc2lvbiBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1tIDJnIFwKICAtdiAvZXRjL21kbS5wZW06L2V0Yy9tZG0ucGVtIFwKICAtdiAvdmFyL2V0dzovdmFyL2V0dzp6IFwKICAkTURNSU1BR0UgXAogIC1DZXJ0RmlsZSAvZXRjL21kbS5wZW0gXAogIC1Gcm9udEVuZFVybCAkTURNRlJPTlRFTkRVUkwgXAogIC1Mb2dnZXIgQ29uc29sZSBcCiAgLUxvZ0xldmVsIFdhcm5pbmcgXAogIC1Qcml2YXRlS2V5RmlsZSAvZXRjL21kbS5wZW0gXAogIC1Tb3VyY2VFbnZpcm9ubWVudCAkTURNU09VUkNFRU5WSVJPTk1FTlQgXAogIC1Tb3VyY2VSb2xlICRNRE1TT1VSQ0VST0xFIFwKICAtU291cmNlUm9sZUluc3RhbmNlICRNRE1TT1VSQ0VST0xFSU5TVEFOQ0UKRXhlY1N0b3A9L3Vzci9iaW4vZG9ja2VyIHN0b3AgJU4KUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tcnAgc2VydmljZSIKY2F0ID4vZXRjL3N5c2NvbmZpZy9hcm8tcnAgPDxFT0YKQUNSX1JFU09VUkNFX0lEPSckQUNSUkVTT1VSQ0VJRCcKQURNSU5fQVBJX0NMSUVOVF9DRVJUX0NPTU1PTl9OQU1FPSckQURNSU5BUElDTElFTlRDRVJUQ09NTU9OTkFNRScKQVJNX0FQSV9DTElFTlRfQ0VSVF9DT01NT05fTkFNRT0nJEFSTUFQSUNMSUVOVENFUlRDT01NT05OQU1FJwpBWlVSRV9BUk1fQ0xJRU5UX0lEPSckQVJNQ0xJRU5USUQnCkFaVVJFX0ZQX0NMSUVOVF9JRD0nJEZQQ0xJRU5USUQnCkFaVVJFX0ZQX1NFUlZJQ0VfUFJJTkNJUEFMX0lEPSckRlBTRVJWSUNFUFJJTkNJUEFMSUQnCkJJTExJTkdfRTJFX1NUT1JBR0VfQUNDT1VOVF9JRD0nJEJJTExJTkdFMkVTVE9SQUdFQUNDT1VOVElEJwpDTFVTVEVSX01EU0RfQUNDT1VOVD0nJENMVVNURVJNRFNEQUNDT1VOVCcKQ0xVU1RFUl9NRFNEX0NPTkZJR19WRVJTSU9OPSckQ0xVU1RFUk1EU0RDT05GSUdWRVJTSU9OJwpDTFVTVEVSX01EU0RfTkFNRVNQQUNFPSckQ0xVU1RFUk1EU0ROQU1FU1BBQ0UnCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCkRPTUFJTl9OQU1FPSckTE9DQVRJT04uJENMVVNURVJQQVJFTlRET01BSU5OQU1FJwpHQVRFV0FZX0RPTUFJTlM9JyRHQVRFV0FZRE9NQUlOUycKR0FURVdBWV9SRVNPVVJDRUdST1VQPSckR0FURVdBWVJFU09VUkNFR1JPVVBOQU1FJwpLRVlWQVVMVF9QUkVGSVg9JyRLRVlWQVVMVFBSRUZJWCcKTURNX0FDQ09VTlQ9JyRSUE1ETUFDQ09VTlQnCk1ETV9OQU1FU1BBQ0U9UlAKTURTRF9FTlZJUk9OTUVOVD0nJE1EU0RFTlZJUk9OTUVOVCcKUlBfRkVBVFVSRVM9JyRSUEZFQVRVUkVTJwpSUElNQUdFPSckUlBJTUFHRScKQVJPX0lOU1RBTExfVklBX0hJVkU9JyRDTFVTVEVSU0lOU1RBTExWSUFISVZFJwpBUk9fSElWRV9ERUZBVUxUX0lOU1RBTExFUl9QVUxMU1BFQz0nJENMVVNURVJERUZBVUxUSU5TVEFMTEVSUFVMTFNQRUMnCkFST19BRE9QVF9CWV9ISVZFPSckQ0xVU1RFUlNBRE9QVEJZSElWRScKVVNFX0NIRUNLQUNDRVNTPSckVVNFQ0hFQ0tBQ0NFU1MnCkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2Fyby1ycC5zZXJ2aWNlIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltTZXJ2aWNlXQpFbnZpcm9ubWVudEZpbGU9L2V0Yy9zeXNjb25maWcvYXJvLXJwCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLWUgQUNSX1JFU09VUkNFX0lEIFwKICAtZSBBRE1JTl9BUElfQ0xJRU5UX0NFUlRfQ09NTU9OX05BTUUgXAogIC1lIEFSTV9BUElfQ0xJRU5UX0NFUlRfQ09NTU9OX05BTUUgXAogIC1lIEFaVVJFX0FSTV9DTElFTlRfSUQgXAogIC1lIEFaVVJFX0ZQX0NMSUVOVF9JRCBcCiAgLWUgQklMTElOR19FMkVfU1RPUkFHRV9BQ0NPVU5UX0lEIFwKICAtZSBDTFVTVEVSX01EU0RfQUNDT1VOVCBcCiAgLWUgQ0xVU1RFUl9NRFNEX0NPTkZJR19WRVJTSU9OIFwKICAtZSBDTFVTVEVSX01EU0RfTkFNRVNQQUNFIFwKICAtZSBEQVRBQkFTRV9BQ0NPVU5UX05BTUUgXAogIC1lIERPTUFJTl9OQU1FIFwKICAtZSBHQVRFV0FZX0RPTUFJTlMgXAogIC1lIEdBVEVXQVlfUkVTT1VSQ0VHUk9VUCBcCiAgLWUgS0VZVkFVTFRfUFJFRklYIFwKICAtZSBNRE1fQUNDT1VOVCBcCiAgLWUgTURNX05BTUVTUEFDRSBcCiAgLWUgTURTRF9FTlZJUk9OTUVOVCBcCiAgLWUgUlBfRkVBVFVSRVMgXAogIC1lIEFST19JTlNUQUxMX1ZJQV9ISVZFIFwKICAtZSBBUk9fSElWRV9ERUZBVUxUX0lOU1RBTExFUl9QVUxMU1BFQyBcCiAgLWUgQVJPX0FET1BUX0JZX0hJVkUgXAogIC1lIFVTRV9DSEVDS0FDQ0VTUyBcCiAgLW0gMmcgXAogIC1wIDQ0Mzo4NDQzIFwKICAtdiAvZXRjL2Fyby1ycDovZXRjL2Fyby1ycCBcCiAgLXYgL3J1bi9zeXN0ZW1kL2pvdXJuYWw6L3J1bi9zeXN0ZW1kL2pvdXJuYWwgXAogIC12IC92YXIvZXR3Oi92YXIvZXR3OnogXAogICRSUElNQUdFIFwKICBycApFeGVjU3RvcD0vdXNyL2Jpbi9kb2NrZXIgc3RvcCAtdCAzNjAwICVOClRpbWVvdXRTdG9wU2VjPTM2MDAKUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tZGJ0b2tlbiBzZXJ2aWNlIgpjYXQgPi9ldGMvc3lzY29uZmlnL2Fyby1kYnRva2VuIDw8RU9GCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCkFaVVJFX0RCVE9LRU5fQ0xJRU5UX0lEPSckREJUT0tFTkNMSUVOVElEJwpBWlVSRV9HQVRFV0FZX1NFUlZJQ0VfUFJJTkNJUEFMX0lEPSckR0FURVdBWVNFUlZJQ0VQUklOQ0lQQUxJRCcKS0VZVkFVTFRfUFJFRklYPSckS0VZVkFVTFRQUkVGSVgnCk1ETV9BQ0NPVU5UPSckUlBNRE1BQ0NPVU5UJwpNRE1fTkFNRVNQQUNFPURCVG9rZW4KUlBJTUFHRT0nJFJQSU1BR0UnCkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2Fyby1kYnRva2VuLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9hcm8tZGJ0b2tlbgpFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1lIEFaVVJFX0dBVEVXQVlfU0VSVklDRV9QUklOQ0lQQUxfSUQgXAogIC1lIERBVEFCQVNFX0FDQ09VTlRfTkFNRSBcCiAgLWUgQVpVUkVfREJUT0tFTl9DTElFTlRfSUQgXAogIC1lIEtFWVZBVUxUX1BSRUZJWCBcCiAgLWUgTURNX0FDQ09VTlQgXAogIC1lIE1ETV9OQU1FU1BBQ0UgXAogIC1tIDJnIFwKICAtcCA0NDU6ODQ0NSBcCiAgLXYgL3J1bi9zeXN0ZW1kL2pvdXJuYWw6L3J1bi9zeXN0ZW1kL2pvdXJuYWwgXAogIC12IC92YXIvZXR3Oi92YXIvZXR3OnogXAogICRSUElNQUdFIFwKICBkYnRva2VuCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wIC10IDM2MDAgJU4KVGltZW91dFN0b3BTZWM9MzYwMApSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIGFyby1tb25pdG9yIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvYXJvLW1vbml0b3IgPDxFT0YKQ0xVU1RFUl9NRE1fQUNDT1VOVD0nJENMVVNURVJNRE1BQ0NPVU5UJwpDTFVTVEVSX01ETV9OQU1FU1BBQ0U9QkJNCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCktFWVZBVUxUX1BSRUZJWD0nJEtFWVZBVUxUUFJFRklYJwpNRE1fQUNDT1VOVD0nJFJQTURNQUNDT1VOVCcKTURNX05BTUVTUEFDRT1CQk0KUlBJTUFHRT0nJFJQSU1BR0UnCkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2Fyby1tb25pdG9yLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9hcm8tbW9uaXRvcgpFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1lIENMVVNURVJfTURNX0FDQ09VTlQgXAogIC1lIENMVVNURVJfTURNX05BTUVTUEFDRSBcCiAgLWUgREFUQUJBU0VfQUNDT1VOVF9OQU1FIFwKICAtZSBLRVlWQVVMVF9QUkVGSVggXAogIC1lIE1ETV9BQ0NPVU5UIFwKICAtZSBNRE1fTkFNRVNQQUNFIFwKICAtbSAyLjVnIFwKICAtdiAvcnVuL3N5c3RlbWQvam91cm5hbDovcnVuL3N5c3RlbWQvam91cm5hbCBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJFJQSU1BR0UgXAogIG1vbml0b3IKUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tcG9ydGFsIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvYXJvLXBvcnRhbCA8PEVPRgpBWlVSRV9QT1JUQUxfQUNDRVNTX0dST1VQX0lEUz0nJFBPUlRBTEFDQ0VTU0dST1VQSURTJwpBWlVSRV9QT1JUQUxfQ0xJRU5UX0lEPSckUE9SVEFMQ0xJRU5USUQnCkFaVVJFX1BPUlRBTF9FTEVWQVRFRF9HUk9VUF9JRFM9JyRQT1JUQUxFTEVWQVRFREdST1VQSURTJwpEQVRBQkFTRV9BQ0NPVU5UX05BTUU9JyREQVRBQkFTRUFDQ09VTlROQU1FJwpLRVlWQVVMVF9QUkVGSVg9JyRLRVlWQVVMVFBSRUZJWCcKTURNX0FDQ09VTlQ9JyRSUE1ETUFDQ09VTlQnCk1ETV9OQU1FU1BBQ0U9UG9ydGFsClBPUlRBTF9IT1NUTkFNRT0nJExPQ0FUSU9OLmFkbWluLiRSUFBBUkVOVERPTUFJTk5BTUUnClJQSU1BR0U9JyRSUElNQUdFJwpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9hcm8tcG9ydGFsLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldApTdGFydExpbWl0SW50ZXJ2YWw9MAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9hcm8tcG9ydGFsCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLWUgQVpVUkVfUE9SVEFMX0FDQ0VTU19HUk9VUF9JRFMgXAogIC1lIEFaVVJFX1BPUlRBTF9DTElFTlRfSUQgXAogIC1lIEFaVVJFX1BPUlRBTF9FTEVWQVRFRF9HUk9VUF9JRFMgXAogIC1lIERBVEFCQVNFX0FDQ09VTlRfTkFNRSBcCiAgLWUgS0VZVkFVTFRfUFJFRklYIFwKICAtZSBNRE1fQUNDT1VOVCBcCiAgLWUgTURNX05BTUVTUEFDRSBcCiAgLWUgUE9SVEFMX0hPU1ROQU1FIFwKICAtbSAyZyBcCiAgLXAgNDQ0Ojg0NDQgXAogIC1wIDIyMjI6MjIyMiBcCiAgLXYgL3J1bi9zeXN0ZW1kL2pvdXJuYWw6L3J1bi9zeXN0ZW1kL2pvdXJuYWwgXAogIC12IC92YXIvZXR3Oi92YXIvZXR3OnogXAogICRSUElNQUdFIFwKICBwb3J0YWwKUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBtZHNkIGFuZCBtZG0gc2VydmljZXMiCmNoY29uIC1SIHN5c3RlbV91Om9iamVjdF9yOnZhcl9sb2dfdDpzMCAvdmFyL29wdC9taWNyb3NvZnQvbGludXhtb25hZ2VudAoKbWtkaXIgLXAgL3Zhci9saWIvd2FhZ2VudC9NaWNyb3NvZnQuQXp1cmUuS2V5VmF1bHQuU3RvcmUKCmZvciB2YXIgaW4gIm1kc2QiICJtZG0iOyBkbwpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZG93bmxvYWQtJHZhci1jcmVkZW50aWFscy5zZXJ2aWNlIDw8RU9GCltVbml0XQpEZXNjcmlwdGlvbj1QZXJpb2RpYyAkdmFyIGNyZWRlbnRpYWxzIHJlZnJlc2gKCltTZXJ2aWNlXQpUeXBlPW9uZXNob3QKRXhlY1N0YXJ0PS91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoICR2YXIKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZG93bmxvYWQtJHZhci1jcmVkZW50aWFscy50aW1lciA8PEVPRgpbVW5pdF0KRGVzY3JpcHRpb249UGVyaW9kaWMgJHZhciBjcmVkZW50aWFscyByZWZyZXNoCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltUaW1lcl0KT25Cb290U2VjPTBtaW4KT25DYWxlbmRhcj0wLzEyOjAwOjAwCkFjY3VyYWN5U2VjPTVzCgpbSW5zdGFsbF0KV2FudGVkQnk9dGltZXJzLnRhcmdldApFT0YKZG9uZQoKY2F0ID4vdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaCA8PEVPRgojIS9iaW4vYmFzaApzZXQgLWV1CgpDT01QT05FTlQ9IlwkMSIKZWNobyAiRG93bmxvYWQgXCRDT01QT05FTlQgY3JlZGVudGlhbHMiCgpURU1QX0RJUj1cJChta3RlbXAgLWQpCmV4cG9ydCBBWlVSRV9DT05GSUdfRElSPVwkKG1rdGVtcCAtZCkKCmVjaG8gIkxvZ2dpbmcgaW50byBBenVyZS4uLiIKUkVUUklFUz0zCndoaWxlIFsgIlwkUkVUUklFUyIgLWd0IDAgXTsgZG8KICAgIGlmIGF6IGxvZ2luIC1pIC0tYWxsb3ctbm8tc3Vic2NyaXB0aW9ucwogICAgdGhlbgogICAgICAgIGVjaG8gImF6IGxvZ2luIHN1Y2Nlc3NmdWwiCiAgICAgICAgYnJlYWsKICAgIGVsc2UKICAgICAgICBlY2hvICJheiBsb2dpbiBmYWlsZWQuIFJldHJ5aW5nLi4uIgogICAgICAgIGxldCBSRVRSSUVTLT0xCiAgICAgICAgc2xlZXAgNQogICAgZmkKZG9uZQoKdHJhcCAiY2xlYW51cCIgRVhJVAoKY2xlYW51cCgpIHsKICBheiBsb2dvdXQKICBbWyAiXCRURU1QX0RJUiIgPX4gL3RtcC8uKyBdXSAmJiBybSAtcmYgXCRURU1QX0RJUgogIFtbICJcJEFaVVJFX0NPTkZJR19ESVIiID1+IC90bXAvLisgXV0gJiYgcm0gLXJmIFwkQVpVUkVfQ09ORklHX0RJUgp9CgppZiBbICJcJENPTVBPTkVOVCIgPSAibWRtIiBdOyB0aGVuCiAgQ1VSUkVOVF9DRVJUX0ZJTEU9Ii9ldGMvbWRtLnBlbSIKZWxpZiBbICJcJENPTVBPTkVOVCIgPSAibWRzZCIgXTsgdGhlbgogIENVUlJFTlRfQ0VSVF9GSUxFPSIvdmFyL2xpYi93YWFnZW50L01pY3Jvc29mdC5BenVyZS5LZXlWYXVsdC5TdG9yZS9tZHNkLnBlbSIKZWxzZQogIGVjaG8gSW52YWxpZCB1c2FnZSAmJiBleGl0IDEKZmkKClNFQ1JFVF9OQU1FPSJycC1cJHtDT01QT05FTlR9IgpORVdfQ0VSVF9GSUxFPSJcJFRFTVBfRElSL1wkQ09NUE9ORU5ULnBlbSIKZm9yIGF0dGVtcHQgaW4gezEuLjV9OyBkbwogIGF6IGtleXZhdWx0IHNlY3JldCBkb3dubG9hZCAtLWZpbGUgXCRORVdfQ0VSVF9GSUxFIC0taWQgImh0dHBzOi8vJEtFWVZBVUxUUFJFRklYLXN2Yy4kS0VZVkFVTFRETlNTVUZGSVgvc2VjcmV0cy9cJFNFQ1JFVF9OQU1FIiAmJiBicmVhawogIGlmIFtbIFwkYXR0ZW1wdCAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmlmIFsgLWYgXCRORVdfQ0VSVF9GSUxFIF07IHRoZW4KICBpZiBbICJcJENPTVBPTkVOVCIgPSAibWRzZCIgXTsgdGhlbgogICAgY2hvd24gc3lzbG9nOnN5c2xvZyBcJE5FV19DRVJUX0ZJTEUKICBlbHNlCiAgICBzZWQgLWkgLW5lICcxLC9FTkQgQ0VSVElGSUNBVEUvIHAnIFwkTkVXX0NFUlRfRklMRQogIGZpCiAgaWYgISBkaWZmICRORVdfQ0VSVF9GSUxFICRDVVJSRU5UX0NFUlRfRklMRSA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICAgIGNobW9kIDA2MDAgXCRORVdfQ0VSVF9GSUxFCiAgICBtdiBcJE5FV19DRVJUX0ZJTEUgXCRDVVJSRU5UX0NFUlRfRklMRQogIGZpCmVsc2UKICBlY2hvIEZhaWxlZCB0byByZWZyZXNoIGNlcnRpZmljYXRlIGZvciBcJENPTVBPTkVOVCAmJiBleGl0IDEKZmkKRU9GCgpjaG1vZCB1K3ggL3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2gKCnN5c3RlbWN0bCBlbmFibGUgZG93bmxvYWQtbWRzZC1jcmVkZW50aWFscy50aW1lcgpzeXN0ZW1jdGwgZW5hYmxlIGRvd25sb2FkLW1kbS1jcmVkZW50aWFscy50aW1lcgoKL3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2ggbWRzZAovdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaCBtZG0KTURTRENFUlRJRklDQVRFU0FOPSQob3BlbnNzbCB4NTA5IC1pbiAvdmFyL2xpYi93YWFnZW50L01pY3Jvc29mdC5BenVyZS5LZXlWYXVsdC5TdG9yZS9tZHNkLnBlbSAtbm9vdXQgLXN1YmplY3QgfCBzZWQgLWUgJ3MvLipDTiA9IC8vJykKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS93YXRjaC1tZG0tY3JlZGVudGlhbHMuc2VydmljZSA8PEVPRgpbVW5pdF0KRGVzY3JpcHRpb249V2F0Y2ggZm9yIGNoYW5nZXMgaW4gbWRtLnBlbSBhbmQgcmVzdGFydHMgdGhlIG1kbSBzZXJ2aWNlCgpbU2VydmljZV0KVHlwZT1vbmVzaG90CkV4ZWNTdGFydD0vdXNyL2Jpbi9zeXN0ZW1jdGwgcmVzdGFydCBtZG0uc2VydmljZQoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL3dhdGNoLW1kbS1jcmVkZW50aWFscy5wYXRoIDw8RU9GCltQYXRoXQpQYXRoTW9kaWZpZWQ9L2V0Yy9tZG0ucGVtCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgpzeXN0ZW1jdGwgZW5hYmxlIHdhdGNoLW1kbS1jcmVkZW50aWFscy5wYXRoCnN5c3RlbWN0bCBzdGFydCB3YXRjaC1tZG0tY3JlZGVudGlhbHMucGF0aAoKbWtkaXIgL2V0Yy9zeXN0ZW1kL3N5c3RlbS9tZHNkLnNlcnZpY2UuZApjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vbWRzZC5zZXJ2aWNlLmQvb3ZlcnJpZGUuY29uZiA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKRU9GCgpjYXQgPi9ldGMvZGVmYXVsdC9tZHNkIDw8RU9GCk1EU0RfUk9MRV9QUkVGSVg9L3Zhci9ydW4vbWRzZC9kZWZhdWx0Ck1EU0RfT1BUSU9OUz0iLUEgLWQgLXIgXCRNRFNEX1JPTEVfUFJFRklYIgoKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0VOVklST05NRU5UPSckTURTREVOVklST05NRU5UJwpleHBvcnQgTU9OSVRPUklOR19HQ1NfQUNDT1VOVD0nJFJQTURTREFDQ09VTlQnCmV4cG9ydCBNT05JVE9SSU5HX0dDU19SRUdJT049JyRMT0NBVElPTicKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0FVVEhfSURfVFlQRT1BdXRoS2V5VmF1bHQKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0FVVEhfSUQ9JyRNRFNEQ0VSVElGSUNBVEVTQU4nCmV4cG9ydCBNT05JVE9SSU5HX0dDU19OQU1FU1BBQ0U9JyRSUE1EU0ROQU1FU1BBQ0UnCmV4cG9ydCBNT05JVE9SSU5HX0NPTkZJR19WRVJTSU9OPSckUlBNRFNEQ09ORklHVkVSU0lPTicKZXhwb3J0IE1PTklUT1JJTkdfVVNFX0dFTkVWQV9DT05GSUdfU0VSVklDRT10cnVlCgpleHBvcnQgTU9OSVRPUklOR19URU5BTlQ9JyRMT0NBVElPTicKZXhwb3J0IE1PTklUT1JJTkdfUk9MRT1ycApleHBvcnQgTU9OSVRPUklOR19ST0xFX0lOU1RBTkNFPSckKGhvc3RuYW1lKScKCmV4cG9ydCBNRFNEX01TR1BBQ0tfU09SVF9DT0xVTU5TPTEKRU9GCgojIHNldHRpbmcgTU9OSVRPUklOR19HQ1NfQVVUSF9JRF9UWVBFPUF1dGhLZXlWYXVsdCBzZWVtcyB0byBoYXZlIGNhdXNlZCBtZHNkIG5vdAojIHRvIGhvbm91ciBTU0xfQ0VSVF9GSUxFIGFueSBtb3JlLCBoZWF2ZW4gb25seSBrbm93cyB3aHkuCm1rZGlyIC1wIC91c3IvbGliL3NzbC9jZXJ0cwpjc3BsaXQgLWYgL3Vzci9saWIvc3NsL2NlcnRzL2NlcnQtIC1iICUwM2QucGVtIC9ldGMvcGtpL3Rscy9jZXJ0cy9jYS1idW5kbGUuY3J0IC9eJC8xIHsqfSA+L2Rldi9udWxsCmNfcmVoYXNoIC91c3IvbGliL3NzbC9jZXJ0cwoKIyB3ZSBsZWF2ZSBjbGllbnRJZCBibGFuayBhcyBsb25nIGFzIG9ubHkgMSBtYW5hZ2VkIGlkZW50aXR5IGFzc2lnbmVkIHRvIHZtc3MKIyBpZiB3ZSBoYXZlIG1vcmUgdGhhbiAxLCB3ZSB3aWxsIG5lZWQgdG8gcG9wdWxhdGUgd2l0aCBjbGllbnRJZCB1c2VkIGZvciBvZmYtbm9kZSBzY2FubmluZwpjYXQgPi9ldGMvZGVmYXVsdC92c2Etbm9kZXNjYW4tYWdlbnQuY29uZmlnIDw8RU9GCnsKICAgICJOaWNlIjogMTksCiAgICAiVGltZW91dCI6IDEwODAwLAogICAgIkNsaWVudElkIjogIiIsCiAgICAiVGVuYW50SWQiOiAiJEFaVVJFU0VDUEFDS1ZTQVRFTkFOVElEIiwKICAgICJRdWFseXNTdG9yZUJhc2VVcmwiOiAiJEFaVVJFU0VDUEFDS1FVQUxZU1VSTCIsCiAgICAiUHJvY2Vzc1RpbWVvdXQiOiAzMDAsCiAgICAiQ29tbWFuZERlbGF5IjogMAogIH0KRU9GCgojIHdlIHN0YXJ0IGEgY3JvbiBqb2IgdG8gcnVuIGV2ZXJ5IGhvdXIgdG8gZW5zdXJlIHRoZSBzYWlkIGRpcmVjdG9yeSBpcyBhY2Nlc3NpYmxlCiMgYnkgdGhlIGNvcnJlY3QgdXNlciBhcyBpdCBnZXRzIGNyZWF0ZWQgYnkgcm9vdCBhbmQgbWF5IGNhdXNlIGEgcmFjZSBjb25kaXRpb24KIyB3aGVyZSByb290IG93bnMgdGhlIGRpciBpbnN0ZWFkIG9mIHN5c2xvZwojIFRPRE86IGh0dHBzOi8vbXNhenVyZS52aXN1YWxzdHVkaW8uY29tL0F6dXJlUmVkSGF0T3BlblNoaWZ0L193b3JraXRlbXMvZWRpdC8xMjU5MTIwNwpjYXQgPi9ldGMvY3Jvbi5kL21kc2QtY2hvd24td29ya2Fyb3VuZCA8PEVPRgpTSEVMTD0vYmluL2Jhc2gKUEFUSD0vYmluCjAgKiAqICogKiByb290IGNob3duIHN5c2xvZzpzeXNsb2cgL3Zhci9vcHQvbWljcm9zb2Z0L2xpbnV4bW9uYWdlbnQvZWgvRXZlbnROb3RpY2UvYXJvcnBsb2dzKgpFT0YKCmVjaG8gImVuYWJsaW5nIGFybyBzZXJ2aWNlcyIKZm9yIHNlcnZpY2UgaW4gYXJvLWRidG9rZW4gYXJvLW1vbml0b3IgYXJvLXBvcnRhbCBhcm8tcnAgYXVvbXMgYXpzZWNkIGF6c2VjbW9uZCBtZHNkIG1kbSBjaHJvbnlkIGZsdWVudGJpdDsgZG8KICBzeXN0ZW1jdGwgZW5hYmxlICRzZXJ2aWNlLnNlcnZpY2UKZG9uZQoKZm9yIHNjYW4gaW4gYmFzZWxpbmUgY2xhbWF2IHNvZnR3YXJlOyBkbwogIC91c3IvbG9jYWwvYmluL2F6c2VjZCBjb25maWcgLXMgJHNjYW4gLWQgUDFECmRvbmUKCmVjaG8gInJlYm9vdGluZyIKcmVzdG9yZWNvbiAtUkYgL3Zhci9sb2cvKgooc2xlZXAgMzA7IHJlYm9vdCkgJgo=')))]" + "script": "[base64(concat(base64ToString('c2V0IC1leAoK'),'ACRRESOURCEID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('acrResourceId')),''')\n','ADMINAPICLIENTCERTCOMMONNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('adminApiClientCertCommonName')),''')\n','ARMAPICLIENTCERTCOMMONNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('armApiClientCertCommonName')),''')\n','ARMCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('armClientId')),''')\n','AZURECLOUDNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureCloudName')),''')\n','AZURESECPACKQUALYSURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureSecPackQualysUrl')),''')\n','AZURESECPACKVSATENANTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('azureSecPackVSATenantId')),''')\n','BILLINGE2ESTORAGEACCOUNTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('billingE2EStorageAccountId')),''')\n','CLUSTERMDMACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdmAccount')),''')\n','CLUSTERMDSDACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdAccount')),''')\n','CLUSTERMDSDCONFIGVERSION=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdConfigVersion')),''')\n','CLUSTERMDSDNAMESPACE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterMdsdNamespace')),''')\n','CLUSTERPARENTDOMAINNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterParentDomainName')),''')\n','DATABASEACCOUNTNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('databaseAccountName')),''')\n','DISABLEOAUTH=$(base64 -d \u003c\u003c\u003c''',base64(parameters('disableOauth')),''')\n','DBTOKENCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('dbtokenClientId')),''')\n','FLUENTBITIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fluentbitImage')),''')\n','FPCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fpClientId')),''')\n','FPSERVICEPRINCIPALID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('fpServicePrincipalId')),''')\n','GATEWAYDOMAINS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayDomains')),''')\n','GATEWAYRESOURCEGROUPNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayResourceGroupName')),''')\n','GATEWAYSERVICEPRINCIPALID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('gatewayServicePrincipalId')),''')\n','KEYVAULTDNSSUFFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultDNSSuffix')),''')\n','KEYVAULTPREFIX=$(base64 -d \u003c\u003c\u003c''',base64(parameters('keyvaultPrefix')),''')\n','MDMFRONTENDURL=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdmFrontendUrl')),''')\n','MDSDENVIRONMENT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('mdsdEnvironment')),''')\n','PORTALACCESSGROUPIDS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalAccessGroupIds')),''')\n','PORTALCLIENTID=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalClientId')),''')\n','PORTALELEVATEDGROUPIDS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('portalElevatedGroupIds')),''')\n','RPFEATURES=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpFeatures')),''')\n','RPIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpImage')),''')\n','RPMDMACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdmAccount')),''')\n','RPMDSDACCOUNT=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdAccount')),''')\n','RPMDSDCONFIGVERSION=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdConfigVersion')),''')\n','RPMDSDNAMESPACE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpMdsdNamespace')),''')\n','RPPARENTDOMAINNAME=$(base64 -d \u003c\u003c\u003c''',base64(parameters('rpParentDomainName')),''')\n','CLUSTERSINSTALLVIAHIVE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clustersInstallViaHive')),''')\n','CLUSTERSADOPTBYHIVE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clustersAdoptByHive')),''')\n','CLUSTERDEFAULTINSTALLERPULLSPEC=$(base64 -d \u003c\u003c\u003c''',base64(parameters('clusterDefaultInstallerPullspec')),''')\n','USECHECKACCESS=$(base64 -d \u003c\u003c\u003c''',base64(parameters('useCheckAccess')),''')\n','ADMINAPICABUNDLE=''',parameters('adminApiCaBundle'),'''\n','ARMAPICABUNDLE=''',parameters('armApiCaBundle'),'''\n','MDMIMAGE=''/genevamdm:2.2023.609.2051-821f47-20230706t0953''\n','LOCATION=$(base64 -d \u003c\u003c\u003c''',base64(resourceGroup().location),''')\n','SUBSCRIPTIONID=$(base64 -d \u003c\u003c\u003c''',base64(subscription().subscriptionId),''')\n','RESOURCEGROUPNAME=$(base64 -d \u003c\u003c\u003c''',base64(resourceGroup().name),''')\n','\n',base64ToString('IyEvYmluL2Jhc2gKCmVjaG8gInNldHRpbmcgc3NoIHBhc3N3b3JkIGF1dGhlbnRpY2F0aW9uIgojIFdlIG5lZWQgdG8gbWFudWFsbHkgc2V0IFBhc3N3b3JkQXV0aGVudGljYXRpb24gdG8gdHJ1ZSBpbiBvcmRlciBmb3IgdGhlIFZNU1MgQWNjZXNzIEpJVCB0byB3b3JrCnNlZCAtaSAncy9QYXNzd29yZEF1dGhlbnRpY2F0aW9uIG5vL1Bhc3N3b3JkQXV0aGVudGljYXRpb24geWVzL2cnIC9ldGMvc3NoL3NzaGRfY29uZmlnCnN5c3RlbWN0bCByZWxvYWQgc3NoZC5zZXJ2aWNlCgplY2hvICJydW5uaW5nIFJIVUkgZml4Igp5dW0gdXBkYXRlIC15IC0tZGlzYWJsZXJlcG89JyonIC0tZW5hYmxlcmVwbz0ncmh1aS1taWNyb3NvZnQtYXp1cmUqJwoKZWNobyAicnVubmluZyB5dW0gdXBkYXRlIgp5dW0gLXkgLXggV0FMaW51eEFnZW50IC14IFdBTGludXhBZ2VudC11ZGV2IHVwZGF0ZSAtLWFsbG93ZXJhc2luZwoKZWNobyAiZXh0ZW5kaW5nIHBhcnRpdGlvbiB0YWJsZSIKIyBMaW51eCBibG9jayBkZXZpY2VzIGFyZSBpbmNvbnNpc3RlbnRseSBuYW1lZAojIGl0J3MgZGlmZmljdWx0IHRvIHRpZSB0aGUgbHZtIHB2IHRvIHRoZSBwaHlzaWNhbCBkaXNrIHVzaW5nIC9kZXYvZGlzayBmaWxlcywgd2hpY2ggaXMgd2h5IGx2cyBpcyB1c2VkIGhlcmUKcGh5c2ljYWxEaXNrPSIkKGx2cyAtbyBkZXZpY2VzIC1hIHwgaGVhZCAtbjIgfCB0YWlsIC1uMSB8IGN1dCAtZCAnICcgLWYgMyB8IGN1dCAtZCBcKCAtZiAxIHwgdHIgLWQgJ1s6ZGlnaXQ6XScpIgpncm93cGFydCAiJHBoeXNpY2FsRGlzayIgMgoKZWNobyAiZXh0ZW5kaW5nIGZpbGVzeXN0ZW1zIgpsdmV4dGVuZCAtbCArMjAlRlJFRSAvZGV2L3Jvb3R2Zy9yb290bHYKeGZzX2dyb3dmcyAvCgpsdmV4dGVuZCAtbCArMTAwJUZSRUUgL2Rldi9yb290dmcvdmFybHYKeGZzX2dyb3dmcyAvdmFyCgplY2hvICJpbXBvcnRpbmcgcnBtIHJlcG9zaXRvcmllcyIKcnBtIC0taW1wb3J0IGh0dHBzOi8vZGwuZmVkb3JhcHJvamVjdC5vcmcvcHViL2VwZWwvUlBNLUdQRy1LRVktRVBFTC04CnJwbSAtLWltcG9ydCBodHRwczovL3BhY2thZ2VzLm1pY3Jvc29mdC5jb20va2V5cy9taWNyb3NvZnQuYXNjCgpmb3IgYXR0ZW1wdCBpbiB7MS4uNX07IGRvCiAgeXVtIC15IGluc3RhbGwgaHR0cHM6Ly9kbC5mZWRvcmFwcm9qZWN0Lm9yZy9wdWIvZXBlbC9lcGVsLXJlbGVhc2UtbGF0ZXN0LTgubm9hcmNoLnJwbSAmJiBicmVhawogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDUgXV07IHRoZW4gc2xlZXAgMTA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgplY2hvICJjb25maWd1cmluZyBsb2dyb3RhdGUiCmNhdCA+L2V0Yy9sb2dyb3RhdGUuY29uZiA8PCdFT0YnCiMgc2VlICJtYW4gbG9ncm90YXRlIiBmb3IgZGV0YWlscwojIHJvdGF0ZSBsb2cgZmlsZXMgd2Vla2x5CndlZWtseQoKIyBrZWVwIDIgd2Vla3Mgd29ydGggb2YgYmFja2xvZ3MKcm90YXRlIDIKCiMgY3JlYXRlIG5ldyAoZW1wdHkpIGxvZyBmaWxlcyBhZnRlciByb3RhdGluZyBvbGQgb25lcwpjcmVhdGUKCiMgdXNlIGRhdGUgYXMgYSBzdWZmaXggb2YgdGhlIHJvdGF0ZWQgZmlsZQpkYXRlZXh0CgojIHVuY29tbWVudCB0aGlzIGlmIHlvdSB3YW50IHlvdXIgbG9nIGZpbGVzIGNvbXByZXNzZWQKY29tcHJlc3MKCiMgUlBNIHBhY2thZ2VzIGRyb3AgbG9nIHJvdGF0aW9uIGluZm9ybWF0aW9uIGludG8gdGhpcyBkaXJlY3RvcnkKaW5jbHVkZSAvZXRjL2xvZ3JvdGF0ZS5kCgojIG5vIHBhY2thZ2VzIG93biB3dG1wIGFuZCBidG1wIC0tIHdlJ2xsIHJvdGF0ZSB0aGVtIGhlcmUKL3Zhci9sb2cvd3RtcCB7CiAgICBtb250aGx5CiAgICBjcmVhdGUgMDY2NCByb290IHV0bXAKICAgICAgICBtaW5zaXplIDFNCiAgICByb3RhdGUgMQp9CgovdmFyL2xvZy9idG1wIHsKICAgIG1pc3NpbmdvawogICAgbW9udGhseQogICAgY3JlYXRlIDA2MDAgcm9vdCB1dG1wCiAgICByb3RhdGUgMQp9CkVPRgoKZWNobyAiY29uZmlndXJpbmcgeXVtIHJlcG9zaXRvcnkgYW5kIHJ1bm5pbmcgeXVtIHVwZGF0ZSIKY2F0ID4vZXRjL3l1bS5yZXBvcy5kL2F6dXJlLnJlcG8gPDwnRU9GJwpbYXp1cmUtY2xpXQpuYW1lPWF6dXJlLWNsaQpiYXNldXJsPWh0dHBzOi8vcGFja2FnZXMubWljcm9zb2Z0LmNvbS95dW1yZXBvcy9henVyZS1jbGkKZW5hYmxlZD15ZXMKZ3BnY2hlY2s9eWVzCgpbYXp1cmVjb3JlXQpuYW1lPWF6dXJlY29yZQpiYXNldXJsPWh0dHBzOi8vcGFja2FnZXMubWljcm9zb2Z0LmNvbS95dW1yZXBvcy9henVyZWNvcmUKZW5hYmxlZD15ZXMKZ3BnY2hlY2s9bm8KRU9GCgpzZW1hbmFnZSBmY29udGV4dCAtYSAtdCB2YXJfbG9nX3QgIi92YXIvbG9nL2pvdXJuYWwoLy4qKT8iCm1rZGlyIC1wIC92YXIvbG9nL2pvdXJuYWwKCmZvciBhdHRlbXB0IGluIHsxLi41fTsgZG8KeXVtIC15IGluc3RhbGwgY2xhbWF2IGF6c2VjLWNsYW1hdiBhenNlYy1tb25pdG9yIGF6dXJlLWNsaSBhenVyZS1tZHNkIGF6dXJlLXNlY3VyaXR5IHBvZG1hbiBwb2RtYW4tZG9ja2VyIG9wZW5zc2wtcGVybCBweXRob24zICYmIGJyZWFrCiAgIyBoYWNrIC0gd2UgYXJlIGluc3RhbGxpbmcgcHl0aG9uMyBvbiBob3N0cyBkdWUgdG8gYW4gaXNzdWUgd2l0aCBBenVyZSBMaW51eCBFeHRlbnNpb25zIGh0dHBzOi8vZ2l0aHViLmNvbS9BenVyZS9henVyZS1saW51eC1leHRlbnNpb25zL3B1bGwvMTUwNQogIGlmIFtbICR7YXR0ZW1wdH0gLWx0IDUgXV07IHRoZW4gc2xlZXAgMTA7IGVsc2UgZXhpdCAxOyBmaQpkb25lCgojIGh0dHBzOi8vYWNjZXNzLnJlZGhhdC5jb20vc2VjdXJpdHkvY3ZlL2N2ZS0yMDIwLTEzNDAxCmVjaG8gImFwcGx5aW5nIGZpcmV3YWxsIHJ1bGVzIgpjYXQgPi9ldGMvc3lzY3RsLmQvMDItZGlzYWJsZS1hY2NlcHQtcmEuY29uZiA8PCdFT0YnCm5ldC5pcHY2LmNvbmYuYWxsLmFjY2VwdF9yYT0wCkVPRgoKY2F0ID4vZXRjL3N5c2N0bC5kLzAxLWRpc2FibGUtY29yZS5jb25mIDw8J0VPRicKa2VybmVsLmNvcmVfcGF0dGVybiA9IHwvYmluL3RydWUKRU9GCnN5c2N0bCAtLXN5c3RlbQoKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9NDQzL3RjcCAtLXBlcm1hbmVudApmaXJld2FsbC1jbWQgLS1hZGQtcG9ydD00NDQvdGNwIC0tcGVybWFuZW50CmZpcmV3YWxsLWNtZCAtLWFkZC1wb3J0PTQ0NS90Y3AgLS1wZXJtYW5lbnQKZmlyZXdhbGwtY21kIC0tYWRkLXBvcnQ9MjIyMi90Y3AgLS1wZXJtYW5lbnQKCmV4cG9ydCBBWlVSRV9DTE9VRF9OQU1FPSRBWlVSRUNMT1VETkFNRQoKZWNobyAibG9nZ2luZyBpbnRvIHByb2QgYWNyIgpheiBsb2dpbiAtaSAtLWFsbG93LW5vLXN1YnNjcmlwdGlvbnMKCiMgU3VwcHJlc3MgZW11bGF0aW9uIG91dHB1dCBmb3IgcG9kbWFuIGluc3RlYWQgb2YgZG9ja2VyIGZvciBheiBhY3IgY29tcGF0YWJpbGl0eQpta2RpciAtcCAvZXRjL2NvbnRhaW5lcnMvCnRvdWNoIC9ldGMvY29udGFpbmVycy9ub2RvY2tlcgoKbWtkaXIgLXAgL3Jvb3QvLmRvY2tlcgpSRUdJU1RSWV9BVVRIX0ZJTEU9L3Jvb3QvLmRvY2tlci9jb25maWcuanNvbiBheiBhY3IgbG9naW4gLS1uYW1lICIkKHNlZCAtZSAnc3wuKi98fCcgPDw8IiRBQ1JSRVNPVVJDRUlEIikiCgpNRE1JTUFHRT0iJHtSUElNQUdFJSUvKn0vJHtNRE1JTUFHRSMjKi99Igpkb2NrZXIgcHVsbCAiJE1ETUlNQUdFIgpkb2NrZXIgcHVsbCAiJFJQSU1BR0UiCmRvY2tlciBwdWxsICIkRkxVRU5UQklUSU1BR0UiCgpheiBsb2dvdXQKCmVjaG8gImNvbmZpZ3VyaW5nIGZsdWVudGJpdCBzZXJ2aWNlIgpta2RpciAtcCAvZXRjL2ZsdWVudGJpdC8KbWtkaXIgLXAgL3Zhci9saWIvZmx1ZW50CgpjYXQgPi9ldGMvZmx1ZW50Yml0L2ZsdWVudGJpdC5jb25mIDw8J0VPRicKW0lOUFVUXQoJTmFtZSBzeXN0ZW1kCglUYWcgam91cm5hbGQKCVN5c3RlbWRfRmlsdGVyIF9DT01NPWFybwoJREIgL3Zhci9saWIvZmx1ZW50L2pvdXJuYWxkYgoKW0ZJTFRFUl0KCU5hbWUgbW9kaWZ5CglNYXRjaCBqb3VybmFsZAoJUmVtb3ZlX3dpbGRjYXJkIF8KCVJlbW92ZSBUSU1FU1RBTVAKCltGSUxURVJdCglOYW1lIHJld3JpdGVfdGFnCglNYXRjaCBqb3VybmFsZAoJUnVsZSAkTE9HS0lORCBhc3luY3FvcyBhc3luY3FvcyB0cnVlCgpbRklMVEVSXQoJTmFtZSBtb2RpZnkKCU1hdGNoIGFzeW5jcW9zCglSZW1vdmUgQ0xJRU5UX1BSSU5DSVBBTF9OQU1FCglSZW1vdmUgRklMRQoJUmVtb3ZlIENPTVBPTkVOVAoKW0ZJTFRFUl0KCU5hbWUgcmV3cml0ZV90YWcKCU1hdGNoIGpvdXJuYWxkCglSdWxlICRMT0dLSU5EIGlmeGF1ZGl0IGlmeGF1ZGl0IGZhbHNlCgpbT1VUUFVUXQoJTmFtZSBmb3J3YXJkCglNYXRjaCAqCglQb3J0IDI5MjMwCkVPRgoKZWNobyAiRkxVRU5UQklUSU1BR0U9JEZMVUVOVEJJVElNQUdFIiA+L2V0Yy9zeXNjb25maWcvZmx1ZW50Yml0CgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZmx1ZW50Yml0LnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldApTdGFydExpbWl0SW50ZXJ2YWxTZWM9MAoKW1NlcnZpY2VdClJlc3RhcnRTZWM9MXMKRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL2ZsdWVudGJpdApFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1zZWN1cml0eS1vcHQgbGFiZWw9ZGlzYWJsZSBcCiAgLS1lbnRyeXBvaW50IC9vcHQvdGQtYWdlbnQtYml0L2Jpbi90ZC1hZ2VudC1iaXQgXAogIC0tbmV0PWhvc3QgXAogIC0taG9zdG5hbWUgJUggXAogIC0tbmFtZSAlTiBcCiAgLS1ybSBcCiAgLS1jYXAtZHJvcCBuZXRfcmF3IFwKICAtdiAvZXRjL2ZsdWVudGJpdC9mbHVlbnRiaXQuY29uZjovZXRjL2ZsdWVudGJpdC9mbHVlbnRiaXQuY29uZiBcCiAgLXYgL3Zhci9saWIvZmx1ZW50Oi92YXIvbGliL2ZsdWVudDp6IFwKICAtdiAvdmFyL2xvZy9qb3VybmFsOi92YXIvbG9nL2pvdXJuYWw6cm8gXAogIC12IC9ldGMvbWFjaGluZS1pZDovZXRjL21hY2hpbmUtaWQ6cm8gXAogICRGTFVFTlRCSVRJTUFHRSBcCiAgLWMgL2V0Yy9mbHVlbnRiaXQvZmx1ZW50Yml0LmNvbmYKCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wICVOClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9NQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKbWtkaXIgL2V0Yy9hcm8tcnAKYmFzZTY0IC1kIDw8PCIkQURNSU5BUElDQUJVTkRMRSIgPi9ldGMvYXJvLXJwL2FkbWluLWNhLWJ1bmRsZS5wZW0KaWYgW1sgLW4gIiRBUk1BUElDQUJVTkRMRSIgXV07IHRoZW4KICBiYXNlNjQgLWQgPDw8IiRBUk1BUElDQUJVTkRMRSIgPi9ldGMvYXJvLXJwL2FybS1jYS1idW5kbGUucGVtCmZpCmNob3duIC1SIDEwMDA6MTAwMCAvZXRjL2Fyby1ycAoKZWNobyAiY29uZmlndXJpbmcgbWRtIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvbWRtIDw8RU9GCk1ETUZST05URU5EVVJMPSckTURNRlJPTlRFTkRVUkwnCk1ETUlNQUdFPSckTURNSU1BR0UnCk1ETVNPVVJDRUVOVklST05NRU5UPSckTE9DQVRJT04nCk1ETVNPVVJDRVJPTEU9cnAKTURNU09VUkNFUk9MRUlOU1RBTkNFPSckKGhvc3RuYW1lKScKRU9GCgpta2RpciAvdmFyL2V0dwpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vbWRtLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9tZG0KRXhlY1N0YXJ0UHJlPS0vdXNyL2Jpbi9kb2NrZXIgcm0gLWYgJU4KRXhlY1N0YXJ0PS91c3IvYmluL2RvY2tlciBydW4gXAogIC0tZW50cnlwb2ludCAvdXNyL3NiaW4vTWV0cmljc0V4dGVuc2lvbiBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1tIDJnIFwKICAtdiAvZXRjL21kbS5wZW06L2V0Yy9tZG0ucGVtIFwKICAtdiAvdmFyL2V0dzovdmFyL2V0dzp6IFwKICAkTURNSU1BR0UgXAogIC1DZXJ0RmlsZSAvZXRjL21kbS5wZW0gXAogIC1Gcm9udEVuZFVybCAkTURNRlJPTlRFTkRVUkwgXAogIC1Mb2dnZXIgQ29uc29sZSBcCiAgLUxvZ0xldmVsIFdhcm5pbmcgXAogIC1Qcml2YXRlS2V5RmlsZSAvZXRjL21kbS5wZW0gXAogIC1Tb3VyY2VFbnZpcm9ubWVudCAkTURNU09VUkNFRU5WSVJPTk1FTlQgXAogIC1Tb3VyY2VSb2xlICRNRE1TT1VSQ0VST0xFIFwKICAtU291cmNlUm9sZUluc3RhbmNlICRNRE1TT1VSQ0VST0xFSU5TVEFOQ0UKRXhlY1N0b3A9L3Vzci9iaW4vZG9ja2VyIHN0b3AgJU4KUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tcnAgc2VydmljZSIKY2F0ID4vZXRjL3N5c2NvbmZpZy9hcm8tcnAgPDxFT0YKQUNSX1JFU09VUkNFX0lEPSckQUNSUkVTT1VSQ0VJRCcKQURNSU5fQVBJX0NMSUVOVF9DRVJUX0NPTU1PTl9OQU1FPSckQURNSU5BUElDTElFTlRDRVJUQ09NTU9OTkFNRScKQVJNX0FQSV9DTElFTlRfQ0VSVF9DT01NT05fTkFNRT0nJEFSTUFQSUNMSUVOVENFUlRDT01NT05OQU1FJwpBWlVSRV9BUk1fQ0xJRU5UX0lEPSckQVJNQ0xJRU5USUQnCkFaVVJFX0ZQX0NMSUVOVF9JRD0nJEZQQ0xJRU5USUQnCkFaVVJFX0ZQX1NFUlZJQ0VfUFJJTkNJUEFMX0lEPSckRlBTRVJWSUNFUFJJTkNJUEFMSUQnCkJJTExJTkdfRTJFX1NUT1JBR0VfQUNDT1VOVF9JRD0nJEJJTExJTkdFMkVTVE9SQUdFQUNDT1VOVElEJwpDTFVTVEVSX01EU0RfQUNDT1VOVD0nJENMVVNURVJNRFNEQUNDT1VOVCcKQ0xVU1RFUl9NRFNEX0NPTkZJR19WRVJTSU9OPSckQ0xVU1RFUk1EU0RDT05GSUdWRVJTSU9OJwpDTFVTVEVSX01EU0RfTkFNRVNQQUNFPSckQ0xVU1RFUk1EU0ROQU1FU1BBQ0UnCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCkRPTUFJTl9OQU1FPSckTE9DQVRJT04uJENMVVNURVJQQVJFTlRET01BSU5OQU1FJwpHQVRFV0FZX0RPTUFJTlM9JyRHQVRFV0FZRE9NQUlOUycKR0FURVdBWV9SRVNPVVJDRUdST1VQPSckR0FURVdBWVJFU09VUkNFR1JPVVBOQU1FJwpLRVlWQVVMVF9QUkVGSVg9JyRLRVlWQVVMVFBSRUZJWCcKTURNX0FDQ09VTlQ9JyRSUE1ETUFDQ09VTlQnCk1ETV9OQU1FU1BBQ0U9UlAKTURTRF9FTlZJUk9OTUVOVD0nJE1EU0RFTlZJUk9OTUVOVCcKUlBfRkVBVFVSRVM9JyRSUEZFQVRVUkVTJwpSUElNQUdFPSckUlBJTUFHRScKQVJPX0lOU1RBTExfVklBX0hJVkU9JyRDTFVTVEVSU0lOU1RBTExWSUFISVZFJwpBUk9fSElWRV9ERUZBVUxUX0lOU1RBTExFUl9QVUxMU1BFQz0nJENMVVNURVJERUZBVUxUSU5TVEFMTEVSUFVMTFNQRUMnCkFST19BRE9QVF9CWV9ISVZFPSckQ0xVU1RFUlNBRE9QVEJZSElWRScKVVNFX0NIRUNLQUNDRVNTPSckVVNFQ0hFQ0tBQ0NFU1MnCkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2Fyby1ycC5zZXJ2aWNlIDw8J0VPRicKW1VuaXRdCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltTZXJ2aWNlXQpFbnZpcm9ubWVudEZpbGU9L2V0Yy9zeXNjb25maWcvYXJvLXJwCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLWUgQUNSX1JFU09VUkNFX0lEIFwKICAtZSBBRE1JTl9BUElfQ0xJRU5UX0NFUlRfQ09NTU9OX05BTUUgXAogIC1lIEFSTV9BUElfQ0xJRU5UX0NFUlRfQ09NTU9OX05BTUUgXAogIC1lIEFaVVJFX0FSTV9DTElFTlRfSUQgXAogIC1lIEFaVVJFX0ZQX0NMSUVOVF9JRCBcCiAgLWUgQklMTElOR19FMkVfU1RPUkFHRV9BQ0NPVU5UX0lEIFwKICAtZSBDTFVTVEVSX01EU0RfQUNDT1VOVCBcCiAgLWUgQ0xVU1RFUl9NRFNEX0NPTkZJR19WRVJTSU9OIFwKICAtZSBDTFVTVEVSX01EU0RfTkFNRVNQQUNFIFwKICAtZSBEQVRBQkFTRV9BQ0NPVU5UX05BTUUgXAogIC1lIERPTUFJTl9OQU1FIFwKICAtZSBHQVRFV0FZX0RPTUFJTlMgXAogIC1lIEdBVEVXQVlfUkVTT1VSQ0VHUk9VUCBcCiAgLWUgS0VZVkFVTFRfUFJFRklYIFwKICAtZSBNRE1fQUNDT1VOVCBcCiAgLWUgTURNX05BTUVTUEFDRSBcCiAgLWUgTURTRF9FTlZJUk9OTUVOVCBcCiAgLWUgUlBfRkVBVFVSRVMgXAogIC1lIEFST19JTlNUQUxMX1ZJQV9ISVZFIFwKICAtZSBBUk9fSElWRV9ERUZBVUxUX0lOU1RBTExFUl9QVUxMU1BFQyBcCiAgLWUgQVJPX0FET1BUX0JZX0hJVkUgXAogIC1lIFVTRV9DSEVDS0FDQ0VTUyBcCiAgLW0gMmcgXAogIC1wIDQ0Mzo4NDQzIFwKICAtdiAvZXRjL2Fyby1ycDovZXRjL2Fyby1ycCBcCiAgLXYgL3J1bi9zeXN0ZW1kL2pvdXJuYWw6L3J1bi9zeXN0ZW1kL2pvdXJuYWwgXAogIC12IC92YXIvZXR3Oi92YXIvZXR3OnogXAogICRSUElNQUdFIFwKICBycApFeGVjU3RvcD0vdXNyL2Jpbi9kb2NrZXIgc3RvcCAtdCAzNjAwICVOClRpbWVvdXRTdG9wU2VjPTM2MDAKUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tZGJ0b2tlbiBzZXJ2aWNlIgpjYXQgPi9ldGMvc3lzY29uZmlnL2Fyby1kYnRva2VuIDw8RU9GCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCkFaVVJFX0RCVE9LRU5fQ0xJRU5UX0lEPSckREJUT0tFTkNMSUVOVElEJwpBWlVSRV9HQVRFV0FZX1NFUlZJQ0VfUFJJTkNJUEFMX0lEPSckR0FURVdBWVNFUlZJQ0VQUklOQ0lQQUxJRCcKS0VZVkFVTFRfUFJFRklYPSckS0VZVkFVTFRQUkVGSVgnCk1ETV9BQ0NPVU5UPSckUlBNRE1BQ0NPVU5UJwpNRE1fTkFNRVNQQUNFPURCVG9rZW4KUlBJTUFHRT0nJFJQSU1BR0UnCkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2Fyby1kYnRva2VuLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9hcm8tZGJ0b2tlbgpFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1lIEFaVVJFX0dBVEVXQVlfU0VSVklDRV9QUklOQ0lQQUxfSUQgXAogIC1lIERBVEFCQVNFX0FDQ09VTlRfTkFNRSBcCiAgLWUgQVpVUkVfREJUT0tFTl9DTElFTlRfSUQgXAogIC1lIEtFWVZBVUxUX1BSRUZJWCBcCiAgLWUgTURNX0FDQ09VTlQgXAogIC1lIE1ETV9OQU1FU1BBQ0UgXAogIC1tIDJnIFwKICAtcCA0NDU6ODQ0NSBcCiAgLXYgL3J1bi9zeXN0ZW1kL2pvdXJuYWw6L3J1bi9zeXN0ZW1kL2pvdXJuYWwgXAogIC12IC92YXIvZXR3Oi92YXIvZXR3OnogXAogICRSUElNQUdFIFwKICBkYnRva2VuCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wIC10IDM2MDAgJU4KVGltZW91dFN0b3BTZWM9MzYwMApSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCmVjaG8gImNvbmZpZ3VyaW5nIGFyby1tb25pdG9yIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvYXJvLW1vbml0b3IgPDxFT0YKQ0xVU1RFUl9NRE1fQUNDT1VOVD0nJENMVVNURVJNRE1BQ0NPVU5UJwpDTFVTVEVSX01ETV9OQU1FU1BBQ0U9QkJNCkRBVEFCQVNFX0FDQ09VTlRfTkFNRT0nJERBVEFCQVNFQUNDT1VOVE5BTUUnCktFWVZBVUxUX1BSRUZJWD0nJEtFWVZBVUxUUFJFRklYJwpNRE1fQUNDT1VOVD0nJFJQTURNQUNDT1VOVCcKTURNX05BTUVTUEFDRT1CQk0KUlBJTUFHRT0nJFJQSU1BR0UnCkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL2Fyby1tb25pdG9yLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9hcm8tbW9uaXRvcgpFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlTgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biBcCiAgLS1ob3N0bmFtZSAlSCBcCiAgLS1uYW1lICVOIFwKICAtLXJtIFwKICAtLWNhcC1kcm9wIG5ldF9yYXcgXAogIC1lIENMVVNURVJfTURNX0FDQ09VTlQgXAogIC1lIENMVVNURVJfTURNX05BTUVTUEFDRSBcCiAgLWUgREFUQUJBU0VfQUNDT1VOVF9OQU1FIFwKICAtZSBLRVlWQVVMVF9QUkVGSVggXAogIC1lIE1ETV9BQ0NPVU5UIFwKICAtZSBNRE1fTkFNRVNQQUNFIFwKICAtbSAyLjVnIFwKICAtdiAvcnVuL3N5c3RlbWQvam91cm5hbDovcnVuL3N5c3RlbWQvam91cm5hbCBcCiAgLXYgL3Zhci9ldHc6L3Zhci9ldHc6eiBcCiAgJFJQSU1BR0UgXAogIG1vbml0b3IKUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xClN0YXJ0TGltaXRJbnRlcnZhbD0wCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBhcm8tcG9ydGFsIHNlcnZpY2UiCmNhdCA+L2V0Yy9zeXNjb25maWcvYXJvLXBvcnRhbCA8PEVPRgpBWlVSRV9QT1JUQUxfQUNDRVNTX0dST1VQX0lEUz0nJFBPUlRBTEFDQ0VTU0dST1VQSURTJwpBWlVSRV9QT1JUQUxfQ0xJRU5UX0lEPSckUE9SVEFMQ0xJRU5USUQnCkFaVVJFX1BPUlRBTF9FTEVWQVRFRF9HUk9VUF9JRFM9JyRQT1JUQUxFTEVWQVRFREdST1VQSURTJwpEQVRBQkFTRV9BQ0NPVU5UX05BTUU9JyREQVRBQkFTRUFDQ09VTlROQU1FJwpLRVlWQVVMVF9QUkVGSVg9JyRLRVlWQVVMVFBSRUZJWCcKTURNX0FDQ09VTlQ9JyRSUE1ETUFDQ09VTlQnCk1ETV9OQU1FU1BBQ0U9UG9ydGFsClBPUlRBTF9IT1NUTkFNRT0nJExPQ0FUSU9OLmFkbWluLiRSUFBBUkVOVERPTUFJTk5BTUUnClJQSU1BR0U9JyRSUElNQUdFJwpESVNBQkxFX09BVVRIPSckRElTQUJMRU9BVVRIJwpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9hcm8tcG9ydGFsLnNlcnZpY2UgPDwnRU9GJwpbVW5pdF0KQWZ0ZXI9bmV0d29yay1vbmxpbmUudGFyZ2V0CldhbnRzPW5ldHdvcmstb25saW5lLnRhcmdldApTdGFydExpbWl0SW50ZXJ2YWw9MAoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9hcm8tcG9ydGFsCkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVOCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIFwKICAtLWhvc3RuYW1lICVIIFwKICAtLW5hbWUgJU4gXAogIC0tcm0gXAogIC0tY2FwLWRyb3AgbmV0X3JhdyBcCiAgLWUgQVpVUkVfUE9SVEFMX0FDQ0VTU19HUk9VUF9JRFMgXAogIC1lIEFaVVJFX1BPUlRBTF9DTElFTlRfSUQgXAogIC1lIEFaVVJFX1BPUlRBTF9FTEVWQVRFRF9HUk9VUF9JRFMgXAogIC1lIERBVEFCQVNFX0FDQ09VTlRfTkFNRSBcCiAgLWUgS0VZVkFVTFRfUFJFRklYIFwKICAtZSBNRE1fQUNDT1VOVCBcCiAgLWUgTURNX05BTUVTUEFDRSBcCiAgLWUgUE9SVEFMX0hPU1ROQU1FIFwKICAtbSAyZyBcCiAgLXAgNDQ0Ojg0NDQgXAogIC1wIDIyMjI6MjIyMiBcCiAgLXYgL3J1bi9zeXN0ZW1kL2pvdXJuYWw6L3J1bi9zeXN0ZW1kL2pvdXJuYWwgXAogIC12IC92YXIvZXR3Oi92YXIvZXR3OnogXAogICRSUElNQUdFIFwKICBwb3J0YWwKUmVzdGFydD1hbHdheXMKUmVzdGFydFNlYz0xCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgplY2hvICJjb25maWd1cmluZyBtZHNkIGFuZCBtZG0gc2VydmljZXMiCmNoY29uIC1SIHN5c3RlbV91Om9iamVjdF9yOnZhcl9sb2dfdDpzMCAvdmFyL29wdC9taWNyb3NvZnQvbGludXhtb25hZ2VudAoKbWtkaXIgLXAgL3Zhci9saWIvd2FhZ2VudC9NaWNyb3NvZnQuQXp1cmUuS2V5VmF1bHQuU3RvcmUKCmZvciB2YXIgaW4gIm1kc2QiICJtZG0iOyBkbwpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZG93bmxvYWQtJHZhci1jcmVkZW50aWFscy5zZXJ2aWNlIDw8RU9GCltVbml0XQpEZXNjcmlwdGlvbj1QZXJpb2RpYyAkdmFyIGNyZWRlbnRpYWxzIHJlZnJlc2gKCltTZXJ2aWNlXQpUeXBlPW9uZXNob3QKRXhlY1N0YXJ0PS91c3IvbG9jYWwvYmluL2Rvd25sb2FkLWNyZWRlbnRpYWxzLnNoICR2YXIKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vZG93bmxvYWQtJHZhci1jcmVkZW50aWFscy50aW1lciA8PEVPRgpbVW5pdF0KRGVzY3JpcHRpb249UGVyaW9kaWMgJHZhciBjcmVkZW50aWFscyByZWZyZXNoCkFmdGVyPW5ldHdvcmstb25saW5lLnRhcmdldApXYW50cz1uZXR3b3JrLW9ubGluZS50YXJnZXQKCltUaW1lcl0KT25Cb290U2VjPTBtaW4KT25DYWxlbmRhcj0wLzEyOjAwOjAwCkFjY3VyYWN5U2VjPTVzCgpbSW5zdGFsbF0KV2FudGVkQnk9dGltZXJzLnRhcmdldApFT0YKZG9uZQoKY2F0ID4vdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaCA8PEVPRgojIS9iaW4vYmFzaApzZXQgLWV1CgpDT01QT05FTlQ9IlwkMSIKZWNobyAiRG93bmxvYWQgXCRDT01QT05FTlQgY3JlZGVudGlhbHMiCgpURU1QX0RJUj1cJChta3RlbXAgLWQpCmV4cG9ydCBBWlVSRV9DT05GSUdfRElSPVwkKG1rdGVtcCAtZCkKCmVjaG8gIkxvZ2dpbmcgaW50byBBenVyZS4uLiIKUkVUUklFUz0zCndoaWxlIFsgIlwkUkVUUklFUyIgLWd0IDAgXTsgZG8KICAgIGlmIGF6IGxvZ2luIC1pIC0tYWxsb3ctbm8tc3Vic2NyaXB0aW9ucwogICAgdGhlbgogICAgICAgIGVjaG8gImF6IGxvZ2luIHN1Y2Nlc3NmdWwiCiAgICAgICAgYnJlYWsKICAgIGVsc2UKICAgICAgICBlY2hvICJheiBsb2dpbiBmYWlsZWQuIFJldHJ5aW5nLi4uIgogICAgICAgIGxldCBSRVRSSUVTLT0xCiAgICAgICAgc2xlZXAgNQogICAgZmkKZG9uZQoKdHJhcCAiY2xlYW51cCIgRVhJVAoKY2xlYW51cCgpIHsKICBheiBsb2dvdXQKICBbWyAiXCRURU1QX0RJUiIgPX4gL3RtcC8uKyBdXSAmJiBybSAtcmYgXCRURU1QX0RJUgogIFtbICJcJEFaVVJFX0NPTkZJR19ESVIiID1+IC90bXAvLisgXV0gJiYgcm0gLXJmIFwkQVpVUkVfQ09ORklHX0RJUgp9CgppZiBbICJcJENPTVBPTkVOVCIgPSAibWRtIiBdOyB0aGVuCiAgQ1VSUkVOVF9DRVJUX0ZJTEU9Ii9ldGMvbWRtLnBlbSIKZWxpZiBbICJcJENPTVBPTkVOVCIgPSAibWRzZCIgXTsgdGhlbgogIENVUlJFTlRfQ0VSVF9GSUxFPSIvdmFyL2xpYi93YWFnZW50L01pY3Jvc29mdC5BenVyZS5LZXlWYXVsdC5TdG9yZS9tZHNkLnBlbSIKZWxzZQogIGVjaG8gSW52YWxpZCB1c2FnZSAmJiBleGl0IDEKZmkKClNFQ1JFVF9OQU1FPSJycC1cJHtDT01QT05FTlR9IgpORVdfQ0VSVF9GSUxFPSJcJFRFTVBfRElSL1wkQ09NUE9ORU5ULnBlbSIKZm9yIGF0dGVtcHQgaW4gezEuLjV9OyBkbwogIGF6IGtleXZhdWx0IHNlY3JldCBkb3dubG9hZCAtLWZpbGUgXCRORVdfQ0VSVF9GSUxFIC0taWQgImh0dHBzOi8vJEtFWVZBVUxUUFJFRklYLXN2Yy4kS0VZVkFVTFRETlNTVUZGSVgvc2VjcmV0cy9cJFNFQ1JFVF9OQU1FIiAmJiBicmVhawogIGlmIFtbIFwkYXR0ZW1wdCAtbHQgNSBdXTsgdGhlbiBzbGVlcCAxMDsgZWxzZSBleGl0IDE7IGZpCmRvbmUKCmlmIFsgLWYgXCRORVdfQ0VSVF9GSUxFIF07IHRoZW4KICBpZiBbICJcJENPTVBPTkVOVCIgPSAibWRzZCIgXTsgdGhlbgogICAgY2hvd24gc3lzbG9nOnN5c2xvZyBcJE5FV19DRVJUX0ZJTEUKICBlbHNlCiAgICBzZWQgLWkgLW5lICcxLC9FTkQgQ0VSVElGSUNBVEUvIHAnIFwkTkVXX0NFUlRfRklMRQogIGZpCiAgaWYgISBkaWZmICRORVdfQ0VSVF9GSUxFICRDVVJSRU5UX0NFUlRfRklMRSA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICAgIGNobW9kIDA2MDAgXCRORVdfQ0VSVF9GSUxFCiAgICBtdiBcJE5FV19DRVJUX0ZJTEUgXCRDVVJSRU5UX0NFUlRfRklMRQogIGZpCmVsc2UKICBlY2hvIEZhaWxlZCB0byByZWZyZXNoIGNlcnRpZmljYXRlIGZvciBcJENPTVBPTkVOVCAmJiBleGl0IDEKZmkKRU9GCgpjaG1vZCB1K3ggL3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2gKCnN5c3RlbWN0bCBlbmFibGUgZG93bmxvYWQtbWRzZC1jcmVkZW50aWFscy50aW1lcgpzeXN0ZW1jdGwgZW5hYmxlIGRvd25sb2FkLW1kbS1jcmVkZW50aWFscy50aW1lcgoKL3Vzci9sb2NhbC9iaW4vZG93bmxvYWQtY3JlZGVudGlhbHMuc2ggbWRzZAovdXNyL2xvY2FsL2Jpbi9kb3dubG9hZC1jcmVkZW50aWFscy5zaCBtZG0KTURTRENFUlRJRklDQVRFU0FOPSQob3BlbnNzbCB4NTA5IC1pbiAvdmFyL2xpYi93YWFnZW50L01pY3Jvc29mdC5BenVyZS5LZXlWYXVsdC5TdG9yZS9tZHNkLnBlbSAtbm9vdXQgLXN1YmplY3QgfCBzZWQgLWUgJ3MvLipDTiA9IC8vJykKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS93YXRjaC1tZG0tY3JlZGVudGlhbHMuc2VydmljZSA8PEVPRgpbVW5pdF0KRGVzY3JpcHRpb249V2F0Y2ggZm9yIGNoYW5nZXMgaW4gbWRtLnBlbSBhbmQgcmVzdGFydHMgdGhlIG1kbSBzZXJ2aWNlCgpbU2VydmljZV0KVHlwZT1vbmVzaG90CkV4ZWNTdGFydD0vdXNyL2Jpbi9zeXN0ZW1jdGwgcmVzdGFydCBtZG0uc2VydmljZQoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKY2F0ID4vZXRjL3N5c3RlbWQvc3lzdGVtL3dhdGNoLW1kbS1jcmVkZW50aWFscy5wYXRoIDw8RU9GCltQYXRoXQpQYXRoTW9kaWZpZWQ9L2V0Yy9tZG0ucGVtCgpbSW5zdGFsbF0KV2FudGVkQnk9bXVsdGktdXNlci50YXJnZXQKRU9GCgpzeXN0ZW1jdGwgZW5hYmxlIHdhdGNoLW1kbS1jcmVkZW50aWFscy5wYXRoCnN5c3RlbWN0bCBzdGFydCB3YXRjaC1tZG0tY3JlZGVudGlhbHMucGF0aAoKbWtkaXIgL2V0Yy9zeXN0ZW1kL3N5c3RlbS9tZHNkLnNlcnZpY2UuZApjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vbWRzZC5zZXJ2aWNlLmQvb3ZlcnJpZGUuY29uZiA8PCdFT0YnCltVbml0XQpBZnRlcj1uZXR3b3JrLW9ubGluZS50YXJnZXQKRU9GCgpjYXQgPi9ldGMvZGVmYXVsdC9tZHNkIDw8RU9GCk1EU0RfUk9MRV9QUkVGSVg9L3Zhci9ydW4vbWRzZC9kZWZhdWx0Ck1EU0RfT1BUSU9OUz0iLUEgLWQgLXIgXCRNRFNEX1JPTEVfUFJFRklYIgoKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0VOVklST05NRU5UPSckTURTREVOVklST05NRU5UJwpleHBvcnQgTU9OSVRPUklOR19HQ1NfQUNDT1VOVD0nJFJQTURTREFDQ09VTlQnCmV4cG9ydCBNT05JVE9SSU5HX0dDU19SRUdJT049JyRMT0NBVElPTicKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0FVVEhfSURfVFlQRT1BdXRoS2V5VmF1bHQKZXhwb3J0IE1PTklUT1JJTkdfR0NTX0FVVEhfSUQ9JyRNRFNEQ0VSVElGSUNBVEVTQU4nCmV4cG9ydCBNT05JVE9SSU5HX0dDU19OQU1FU1BBQ0U9JyRSUE1EU0ROQU1FU1BBQ0UnCmV4cG9ydCBNT05JVE9SSU5HX0NPTkZJR19WRVJTSU9OPSckUlBNRFNEQ09ORklHVkVSU0lPTicKZXhwb3J0IE1PTklUT1JJTkdfVVNFX0dFTkVWQV9DT05GSUdfU0VSVklDRT10cnVlCgpleHBvcnQgTU9OSVRPUklOR19URU5BTlQ9JyRMT0NBVElPTicKZXhwb3J0IE1PTklUT1JJTkdfUk9MRT1ycApleHBvcnQgTU9OSVRPUklOR19ST0xFX0lOU1RBTkNFPSckKGhvc3RuYW1lKScKCmV4cG9ydCBNRFNEX01TR1BBQ0tfU09SVF9DT0xVTU5TPTEKRU9GCgojIHNldHRpbmcgTU9OSVRPUklOR19HQ1NfQVVUSF9JRF9UWVBFPUF1dGhLZXlWYXVsdCBzZWVtcyB0byBoYXZlIGNhdXNlZCBtZHNkIG5vdAojIHRvIGhvbm91ciBTU0xfQ0VSVF9GSUxFIGFueSBtb3JlLCBoZWF2ZW4gb25seSBrbm93cyB3aHkuCm1rZGlyIC1wIC91c3IvbGliL3NzbC9jZXJ0cwpjc3BsaXQgLWYgL3Vzci9saWIvc3NsL2NlcnRzL2NlcnQtIC1iICUwM2QucGVtIC9ldGMvcGtpL3Rscy9jZXJ0cy9jYS1idW5kbGUuY3J0IC9eJC8xIHsqfSA+L2Rldi9udWxsCmNfcmVoYXNoIC91c3IvbGliL3NzbC9jZXJ0cwoKIyB3ZSBsZWF2ZSBjbGllbnRJZCBibGFuayBhcyBsb25nIGFzIG9ubHkgMSBtYW5hZ2VkIGlkZW50aXR5IGFzc2lnbmVkIHRvIHZtc3MKIyBpZiB3ZSBoYXZlIG1vcmUgdGhhbiAxLCB3ZSB3aWxsIG5lZWQgdG8gcG9wdWxhdGUgd2l0aCBjbGllbnRJZCB1c2VkIGZvciBvZmYtbm9kZSBzY2FubmluZwpjYXQgPi9ldGMvZGVmYXVsdC92c2Etbm9kZXNjYW4tYWdlbnQuY29uZmlnIDw8RU9GCnsKICAgICJOaWNlIjogMTksCiAgICAiVGltZW91dCI6IDEwODAwLAogICAgIkNsaWVudElkIjogIiIsCiAgICAiVGVuYW50SWQiOiAiJEFaVVJFU0VDUEFDS1ZTQVRFTkFOVElEIiwKICAgICJRdWFseXNTdG9yZUJhc2VVcmwiOiAiJEFaVVJFU0VDUEFDS1FVQUxZU1VSTCIsCiAgICAiUHJvY2Vzc1RpbWVvdXQiOiAzMDAsCiAgICAiQ29tbWFuZERlbGF5IjogMAogIH0KRU9GCgojIHdlIHN0YXJ0IGEgY3JvbiBqb2IgdG8gcnVuIGV2ZXJ5IGhvdXIgdG8gZW5zdXJlIHRoZSBzYWlkIGRpcmVjdG9yeSBpcyBhY2Nlc3NpYmxlCiMgYnkgdGhlIGNvcnJlY3QgdXNlciBhcyBpdCBnZXRzIGNyZWF0ZWQgYnkgcm9vdCBhbmQgbWF5IGNhdXNlIGEgcmFjZSBjb25kaXRpb24KIyB3aGVyZSByb290IG93bnMgdGhlIGRpciBpbnN0ZWFkIG9mIHN5c2xvZwojIFRPRE86IGh0dHBzOi8vbXNhenVyZS52aXN1YWxzdHVkaW8uY29tL0F6dXJlUmVkSGF0T3BlblNoaWZ0L193b3JraXRlbXMvZWRpdC8xMjU5MTIwNwpjYXQgPi9ldGMvY3Jvbi5kL21kc2QtY2hvd24td29ya2Fyb3VuZCA8PEVPRgpTSEVMTD0vYmluL2Jhc2gKUEFUSD0vYmluCjAgKiAqICogKiByb290IGNob3duIHN5c2xvZzpzeXNsb2cgL3Zhci9vcHQvbWljcm9zb2Z0L2xpbnV4bW9uYWdlbnQvZWgvRXZlbnROb3RpY2UvYXJvcnBsb2dzKgpFT0YKCmVjaG8gImVuYWJsaW5nIGFybyBzZXJ2aWNlcyIKZm9yIHNlcnZpY2UgaW4gYXJvLWRidG9rZW4gYXJvLW1vbml0b3IgYXJvLXBvcnRhbCBhcm8tcnAgYXVvbXMgYXpzZWNkIGF6c2VjbW9uZCBtZHNkIG1kbSBjaHJvbnlkIGZsdWVudGJpdDsgZG8KICBzeXN0ZW1jdGwgZW5hYmxlICRzZXJ2aWNlLnNlcnZpY2UKZG9uZQoKZm9yIHNjYW4gaW4gYmFzZWxpbmUgY2xhbWF2IHNvZnR3YXJlOyBkbwogIC91c3IvbG9jYWwvYmluL2F6c2VjZCBjb25maWcgLXMgJHNjYW4gLWQgUDFECmRvbmUKCmVjaG8gInJlYm9vdGluZyIKcmVzdG9yZWNvbiAtUkYgL3Zhci9sb2cvKgooc2xlZXAgMzA7IHJlYm9vdCkgJgo=')))]" } } } diff --git a/pkg/deploy/config.go b/pkg/deploy/config.go index 449cd6990b8..5a46bf74b67 100644 --- a/pkg/deploy/config.go +++ b/pkg/deploy/config.go @@ -65,6 +65,7 @@ type Configuration struct { ExtraGatewayKeyvaultAccessPolicies []interface{} `json:"extraGatewayKeyvaultAccessPolicies,omitempty" value:"required"` ExtraPortalKeyvaultAccessPolicies []interface{} `json:"extraPortalKeyvaultAccessPolicies,omitempty" value:"required"` ExtraServiceKeyvaultAccessPolicies []interface{} `json:"extraServiceKeyvaultAccessPolicies,omitempty" value:"required"` + DisableOauth *string `json:"disableOauth,omitempty"` FluentbitImage *string `json:"fluentbitImage,omitempty" value:"required"` FPClientID *string `json:"fpClientId,omitempty" value:"required"` FPServerCertCommonName *string `json:"fpServerCertCommonName,omitempty"` diff --git a/pkg/deploy/generator/resources_rp.go b/pkg/deploy/generator/resources_rp.go index a33fa5746ea..7664f57c7b2 100644 --- a/pkg/deploy/generator/resources_rp.go +++ b/pkg/deploy/generator/resources_rp.go @@ -464,6 +464,7 @@ func (g *generator) rpVMSS() *arm.Resource { "clusterMdsdNamespace", "clusterParentDomainName", "databaseAccountName", + "disableOauth", "dbtokenClientId", "fluentbitImage", "fpClientId", diff --git a/pkg/deploy/generator/scripts/rpVMSS.sh b/pkg/deploy/generator/scripts/rpVMSS.sh index 4aabdf99e5a..35c944e4b97 100644 --- a/pkg/deploy/generator/scripts/rpVMSS.sh +++ b/pkg/deploy/generator/scripts/rpVMSS.sh @@ -437,6 +437,7 @@ MDM_ACCOUNT='$RPMDMACCOUNT' MDM_NAMESPACE=Portal PORTAL_HOSTNAME='$LOCATION.admin.$RPPARENTDOMAINNAME' RPIMAGE='$RPIMAGE' +DISABLE_OAUTH='$DISABLEOAUTH' EOF cat >/etc/systemd/system/aro-portal.service <<'EOF' diff --git a/pkg/deploy/generator/templates_rp.go b/pkg/deploy/generator/templates_rp.go index 7452d0fb247..055526be934 100644 --- a/pkg/deploy/generator/templates_rp.go +++ b/pkg/deploy/generator/templates_rp.go @@ -49,6 +49,7 @@ func (g *generator) rpTemplate() *arm.Template { "clusterMdsdNamespace", "cosmosDB", "dbtokenClientId", + "disableOauth", "disableCosmosDBFirewall", "fluentbitImage", "fpClientId", @@ -145,6 +146,8 @@ func (g *generator) rpTemplate() *arm.Template { "clusterDefaultInstallerPullspec", "useCheckAccess": p.DefaultValue = "" + case "disableOauth": + p.DefaultValue = "false" } t.Parameters[param] = p } diff --git a/pkg/portal/http_test.go b/pkg/portal/http_test.go deleted file mode 100644 index 0d49fb6ba3c..00000000000 --- a/pkg/portal/http_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package portal - -// Copyright (c) Microsoft Corporation. -// Licensed under the Apache License 2.0. - -import ( - "context" - "log" - "net" - "net/http" - "testing" - "time" - - "github.com/sirupsen/logrus/hooks/test" - - "github.com/Azure/ARO-RP/pkg/database" - "github.com/Azure/ARO-RP/pkg/env" - frontendmiddleware "github.com/Azure/ARO-RP/pkg/frontend/middleware" - "github.com/Azure/ARO-RP/test/util/listener" - testlog "github.com/Azure/ARO-RP/test/util/log" -) - -type testPortal struct { - p *portal - l *listener.Listener - auditHook *test.Hook - portalLogHook *test.Hook -} - -func NewTestPortal(_env env.Core, dbOpenShiftClusters database.OpenShiftClusters, dbPortal database.Portal) *testPortal { - _, portalAccessLog := testlog.New() - portalLogHook, portalLog := testlog.New() - auditHook, portalAuditLog := testlog.NewAudit() - - l := listener.NewListener() - p := NewPortal(_env, portalAuditLog, portalLog, portalAccessLog, l, nil, nil, "", nil, nil, "", nil, nil, make([]byte, 32), nil, nonElevatedGroupIDs, elevatedGroupIDs, dbOpenShiftClusters, dbPortal, nil, nil).(*portal) - - return &testPortal{ - p: p, - l: l, - auditHook: auditHook, - portalLogHook: portalLogHook, - } -} - -func (p *testPortal) DumpLogs(t *testing.T) { - for _, l := range p.portalLogHook.Entries { - t.Error(l) - } -} - -func (p *testPortal) Run(ctx context.Context) error { - router, err := p.p.setupRouter(nil, nil, nil) - if err != nil { - return err - } - - s := &http.Server{ - Handler: frontendmiddleware.Lowercase(router), - ReadTimeout: 10 * time.Second, - IdleTimeout: 2 * time.Minute, - ErrorLog: log.New(p.p.log.Writer(), "", 0), - BaseContext: func(net.Listener) context.Context { return ctx }, - } - - go func() { - err := s.Serve(p.l) - if err != nil { - p.p.log.Error(err) - } - }() - - return nil -} - -func (p *testPortal) Request(method string, path string, authenticated bool, elevated bool) (*http.Response, error) { - p.portalLogHook.Reset() - - req, err := http.NewRequest(method, "http://server"+path, nil) - if err != nil { - return nil, err - } - - err = addCSRF(req) - if err != nil { - return nil, err - } - - if authenticated { - var groups []string - if elevated { - groups = elevatedGroupIDs - } else { - groups = nonElevatedGroupIDs - } - err = addAuth(req, groups) - if err != nil { - return nil, err - } - } - - c := &http.Client{ - Transport: &http.Transport{ - DialContext: p.p.l.(*listener.Listener).DialContext, - }, - CheckRedirect: func(*http.Request, []*http.Request) error { - return http.ErrUseLastResponse - }, - } - - resp, err := c.Do(req) - if err != nil { - return nil, err - } - return resp, err -} - -func (p *testPortal) Cleanup() { - p.l.Close() -} diff --git a/pkg/portal/info_test.go b/pkg/portal/info_test.go index e98e3e67a02..3c5e7dea59d 100644 --- a/pkg/portal/info_test.go +++ b/pkg/portal/info_test.go @@ -6,54 +6,33 @@ package portal import ( "context" "encoding/json" + "net/http" + "net/http/httptest" "testing" "github.com/go-test/deep" "github.com/golang/mock/gomock" + "github.com/Azure/ARO-RP/pkg/portal/middleware" "github.com/Azure/ARO-RP/pkg/util/azureclient" mock_env "github.com/Azure/ARO-RP/pkg/util/mocks/env" "github.com/Azure/ARO-RP/pkg/util/version" - testdatabase "github.com/Azure/ARO-RP/test/database" ) -func TestInfo(t *testing.T) { - ctx := context.Background() - - controller := gomock.NewController(t) - defer controller.Finish() - - _env := mock_env.NewMockCore(controller) - _env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false) - _env.EXPECT().Location().AnyTimes().Return("eastus") - _env.EXPECT().TenantID().AnyTimes().Return("00000000-0000-0000-0000-000000000001") - _env.EXPECT().Environment().AnyTimes().Return(&azureclient.PublicCloud) - _env.EXPECT().Hostname().AnyTimes().Return("testhost") - - dbOpenShiftClusters, _ := testdatabase.NewFakeOpenShiftClusters() - dbPortal, _ := testdatabase.NewFakePortal() - - p := NewTestPortal(_env, dbOpenShiftClusters, dbPortal) - defer p.Cleanup() - err := p.Run(ctx) - if err != nil { - t.Error(err) - return - } - - for _, tt := range []struct { +func TestPortalInfo(t *testing.T) { + var tests = []struct { name string - expectedResponse PortalInfo - expectedStatusCode int + expected PortalInfo authenticated bool elevated bool + expectedStatusCode int }{ { name: "basic", authenticated: true, elevated: false, expectedStatusCode: 200, - expectedResponse: PortalInfo{ + expected: PortalInfo{ Location: "eastus", Username: "username", Elevated: false, @@ -65,43 +44,68 @@ func TestInfo(t *testing.T) { authenticated: true, elevated: true, expectedStatusCode: 200, - expectedResponse: PortalInfo{ + expected: PortalInfo{ Location: "eastus", Username: "username", Elevated: true, RPVersion: version.GitCommit, }, }, - } { - resp, err := p.Request("GET", "/api/info", tt.authenticated, tt.elevated) - if err != nil { - p.DumpLogs(t) - t.Error(err) - } - defer resp.Body.Close() + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + + controller := gomock.NewController(t) + defer controller.Finish() + + _env := mock_env.NewMockCore(controller) + _env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false) + _env.EXPECT().Location().AnyTimes().Return("eastus") + _env.EXPECT().TenantID().AnyTimes().Return("00000000-0000-0000-0000-000000000001") + _env.EXPECT().Environment().AnyTimes().Return(&azureclient.PublicCloud) + _env.EXPECT().Hostname().AnyTimes().Return("testhost") + + p := NewPortal(_env, nil, nil, nil, nil, nil, nil, "", nil, nil, "", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) + + request, err := http.NewRequest(http.MethodGet, "/api/info", nil) + if err != nil { + t.Fatal("could not create the request ", err) + } + request = request.WithContext(context.WithValue(ctx, middleware.ContextKeyUsername, "username")) + request = request.WithContext(context.WithValue(request.Context(), middleware.ContextKeyGroups, []string{})) + if tt.elevated { + request = request.WithContext(context.WithValue(request.Context(), middleware.ContextKeyGroups, []string{"elevated"})) + } - if resp.StatusCode != tt.expectedStatusCode { - t.Errorf("%d != %d", resp.StatusCode, tt.expectedStatusCode) - } + writer := httptest.NewRecorder() + portal := p.(*portal) + portal.elevatedGroupIDs = []string{"elevated"} + portal.info(writer, request) + if writer.Result().StatusCode != tt.expectedStatusCode { + t.Errorf("%d != %d", writer.Result().StatusCode, tt.expectedStatusCode) + } - if resp.Header.Get("Content-Type") != "application/json" { - t.Error(resp.Header.Get("Content-Type")) - } + if writer.Result().Header.Get("Content-Type") != "application/json" { + t.Error(writer.Result().Header.Get("Content-Type")) + } - var readResp PortalInfo - err = json.NewDecoder(resp.Body).Decode(&readResp) - if err != nil { - t.Fatal(err) - } + var readResp PortalInfo + err = json.NewDecoder(writer.Result().Body).Decode(&readResp) + if err != nil { + t.Fatal(err) + } - // copy through the CSRF token if it's non-blank, since we can't make it - // a known value - if readResp.CSRFToken != "" { - tt.expectedResponse.CSRFToken = readResp.CSRFToken - } + // copy through the CSRF token if it's non-blank, since we can't make it + // a known value + if readResp.CSRFToken != "" { + tt.expected.CSRFToken = readResp.CSRFToken + } - for _, l := range deep.Equal(readResp, tt.expectedResponse) { - t.Error(l) - } + for _, l := range deep.Equal(readResp, tt.expected) { + t.Error(l) + } + }) } } diff --git a/pkg/portal/kubeconfig/proxy_test.go b/pkg/portal/kubeconfig/proxy_test.go index 1b22e622046..fce3f290a40 100644 --- a/pkg/portal/kubeconfig/proxy_test.go +++ b/pkg/portal/kubeconfig/proxy_test.go @@ -440,7 +440,7 @@ func TestProxy(t *testing.T) { } if string(b) != tt.wantBody { - t.Errorf("%q", string(b)) + t.Errorf("wanted %s but got %s", tt.wantBody, string(b)) } }) } diff --git a/pkg/portal/middleware/aad.go b/pkg/portal/middleware/aad.go index a8fecc1df84..9f1b9024b98 100644 --- a/pkg/portal/middleware/aad.go +++ b/pkg/portal/middleware/aad.go @@ -14,20 +14,19 @@ import ( "time" "github.com/Azure/go-autorest/autorest/adal" - "github.com/gorilla/mux" - "github.com/gorilla/securecookie" - "github.com/gorilla/sessions" + "github.com/coreos/go-oidc/v3/oidc" "github.com/sirupsen/logrus" "golang.org/x/oauth2" "github.com/Azure/ARO-RP/pkg/env" - "github.com/Azure/ARO-RP/pkg/util/oidc" "github.com/Azure/ARO-RP/pkg/util/roundtripper" "github.com/Azure/ARO-RP/pkg/util/uuid" ) const ( SessionName = "session" + OIDCCookie = "oidc_cookie" + stateCookie = "oidc_state_cookie" // Expiration time in unix format SessionKeyExpires = "expires" sessionKeyState = "state" @@ -38,7 +37,7 @@ const ( // AAD is responsible for ensuring that we have a valid login session with AAD. type AAD interface { AAD(http.Handler) http.Handler - CheckAuthentication(http.Handler) http.Handler + Callback(w http.ResponseWriter, r *http.Request) Login(http.ResponseWriter, *http.Request) Logout(string) http.Handler } @@ -48,6 +47,16 @@ type oauther interface { Exchange(context.Context, string, ...oauth2.AuthCodeOption) (*oauth2.Token, error) } +// this interface's only purpose is testability because generating JWT is hard +type accessValidator interface { + validateAccess(ctx context.Context, rawIDToken string, now time.Time) (forbidden bool, username string, groups []string, expiry time.Time, err error) +} + +type defaultAccessValidator struct { + allowedGroups []string + verifier *oidc.IDTokenVerifier +} + type claims struct { Groups []string `json:"groups,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` @@ -64,10 +73,9 @@ type aad struct { clientKey *rsa.PrivateKey clientCerts []*x509.Certificate - store *sessions.CookieStore - oauther oauther - verifier oidc.Verifier - allGroups []string + oauther oauther + accessValidator accessValidator + allGroups []string sessionTimeout time.Duration } @@ -82,8 +90,8 @@ func NewAAD(log *logrus.Entry, clientKey *rsa.PrivateKey, clientCerts []*x509.Certificate, allGroups []string, - unauthenticatedRouter *mux.Router, - verifier oidc.Verifier) (*aad, error) { + verifier *oidc.IDTokenVerifier, +) (*aad, error) { if len(sessionKey) != 32 { return nil, errors.New("invalid sessionKey") } @@ -103,7 +111,6 @@ func NewAAD(log *logrus.Entry, clientID: clientID, clientKey: clientKey, clientCerts: clientCerts, - store: sessions.NewCookieStore(sessionKey), oauther: &oauth2.Config{ ClientID: clientID, Endpoint: endpoint, @@ -113,149 +120,78 @@ func NewAAD(log *logrus.Entry, "profile", }, }, - verifier: verifier, - allGroups: allGroups, + accessValidator: defaultAccessValidator{allowedGroups: allGroups, verifier: verifier}, + allGroups: allGroups, sessionTimeout: time.Hour, } - a.store.MaxAge(0) - a.store.Options.Secure = true - a.store.Options.HttpOnly = true - a.store.Options.SameSite = http.SameSiteLaxMode - - unauthenticatedRouter.NewRoute().Methods(http.MethodGet).Path("/callback").Handler(Log(env, audit, baseAccessLog)(http.HandlerFunc(a.callback))) - unauthenticatedRouter.NewRoute().Methods(http.MethodGet).Path("/api/login").Handler(Log(env, audit, baseAccessLog)(http.HandlerFunc(a.Login))) - unauthenticatedRouter.NewRoute().Methods(http.MethodPost).Path("/api/logout").Handler(Log(env, audit, baseAccessLog)(a.Logout("/"))) - return a, nil } // AAD is the early stage handler which adds a username to the context if it // can. It lets the request through regardless (this is so that failures can be // logged). +// Did we though? checkauth didn't do any logging and redirected to login func (a *aad) AAD(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - session, err := a.store.Get(r, SessionName) + oidcToken, err := r.Cookie(OIDCCookie) if err != nil { - cookieError, ok := err.(securecookie.Error) - if ok && cookieError != nil && cookieError.IsDecode() { - cookie := &http.Cookie{ - Name: SessionName, - Path: "/", - Expires: time.Unix(0, 0), - } - http.SetCookie(w, cookie) - http.Redirect(w, r, "/api/login", http.StatusTemporaryRedirect) - } else { - a.internalServerError(w, err) - } + http.Redirect(w, r, "/api/login", http.StatusTemporaryRedirect) return } - - expires, ok := session.Values[SessionKeyExpires].(int64) - if !ok || time.Unix(expires, 0).Before(a.now()) { - h.ServeHTTP(w, r) + forbidden, username, groups, _, err := a.accessValidator.validateAccess(r.Context(), oidcToken.Value, time.Now()) + if forbidden { + a.log.Debug(groups) + http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + if err != nil { + http.Redirect(w, r, "/api/login", http.StatusTemporaryRedirect) return } ctx := r.Context() - ctx = context.WithValue(ctx, ContextKeyUsername, session.Values[SessionKeyUsername]) - ctx = context.WithValue(ctx, ContextKeyGroups, session.Values[SessionKeyGroups]) + ctx = context.WithValue(ctx, ContextKeyUsername, username) + ctx = context.WithValue(ctx, ContextKeyGroups, groups) r = r.WithContext(ctx) h.ServeHTTP(w, r) }) } -// CheckAuthentication is the handler which prevents access to requests without -// valid authentication. -func (a *aad) CheckAuthentication(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - if ctx.Value(ContextKeyUsername) == nil { - if r.URL != nil { - http.Redirect(w, r, "/api/login", http.StatusTemporaryRedirect) - return - } - http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) - return - } - h.ServeHTTP(w, r) +// Login will redirect the user to a OAUTH login page with a state (CSRF protection). +func (a *aad) Login(w http.ResponseWriter, r *http.Request) { + state := uuid.DefaultGenerator.Generate() + + http.SetCookie(w, &http.Cookie{ + Name: stateCookie, + Value: state, + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, }) -} -// Login will redirect the user to a login page. -func (a *aad) Login(w http.ResponseWriter, r *http.Request) { - a.redirect(w, r) + http.Redirect(w, r, a.oauther.AuthCodeURL(state), http.StatusTemporaryRedirect) } func (a *aad) Logout(url string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - session, err := a.store.Get(r, SessionName) - if err != nil { - a.internalServerError(w, err) - return - } - - session.Values = nil - - err = session.Save(r, w) - if err != nil { - a.internalServerError(w, err) - return - } - + http.SetCookie(w, &http.Cookie{Name: OIDCCookie, MaxAge: -1}) http.Redirect(w, r, url, http.StatusSeeOther) }) } -func (a *aad) redirect(w http.ResponseWriter, r *http.Request) { - session, err := a.store.Get(r, SessionName) - if err != nil { - a.internalServerError(w, err) - return - } - - state := uuid.DefaultGenerator.Generate() - - session.Values = map[interface{}]interface{}{ - sessionKeyState: state, - } - - err = session.Save(r, w) - if err != nil { - a.internalServerError(w, err) - return - } - - http.Redirect(w, r, a.oauther.AuthCodeURL(state), http.StatusTemporaryRedirect) -} - -func (a *aad) callback(w http.ResponseWriter, r *http.Request) { +func (a *aad) Callback(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - session, err := a.store.Get(r, SessionName) + state, err := r.Cookie(stateCookie) if err != nil { - a.internalServerError(w, err) + http.Redirect(w, r, "/api/login", http.StatusTemporaryRedirect) return } - state, ok := session.Values[sessionKeyState].(string) - if !ok { - a.redirect(w, r) - return - } - - delete(session.Values, sessionKeyState) - - err = session.Save(r, w) - if err != nil { - a.internalServerError(w, err) - return - } - - if r.FormValue("state") != state { + if r.FormValue("state") != state.Value { a.internalServerError(w, errors.New("state mismatch")) return } @@ -286,35 +222,63 @@ func (a *aad) callback(w http.ResponseWriter, r *http.Request) { return } - idToken, err := a.verifier.Verify(r.Context(), rawIDToken) + _, _, _, expiry, err := a.accessValidator.validateAccess(r.Context(), rawIDToken, a.now()) if err != nil { - a.internalServerError(w, err) + // we could not identify the user so we make them try to login again + http.Redirect(w, r, "/api/login", http.StatusTemporaryRedirect) return } + // only keep the cookie for the validity time of the token + http.SetCookie(w, &http.Cookie{ + Name: OIDCCookie, + Value: rawIDToken, + Expires: expiry, + SameSite: http.SameSiteStrictMode, + Secure: true, + HttpOnly: true, + }) + + // delete the state cookie + http.SetCookie(w, &http.Cookie{ + Name: stateCookie, + Value: "", + MaxAge: -1, + }) + + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) +} + +func extractInfoFromToken(token *oidc.IDToken) (preferredUsername string, groups []string, err error) { var claims claims - err = idToken.Claims(&claims) - if err != nil { - a.internalServerError(w, err) - return - } - groupsIntersect := GroupsIntersect(a.allGroups, claims.Groups) - if len(groupsIntersect) == 0 { - http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) + err = token.Claims(&claims) + if err != nil { + return "", nil, err } - session.Values[SessionKeyUsername] = claims.PreferredUsername - session.Values[SessionKeyGroups] = groupsIntersect - session.Values[SessionKeyExpires] = a.now().Add(a.sessionTimeout).Unix() + return claims.PreferredUsername, claims.Groups, nil +} - err = session.Save(r, w) +func (d defaultAccessValidator) validateAccess(ctx context.Context, rawIDToken string, now time.Time) (forbidden bool, username string, groups []string, expiry time.Time, err error) { + idToken, err := d.verifier.Verify(ctx, rawIDToken) if err != nil { - a.internalServerError(w, err) - return + return false, "", nil, time.Time{}, err } - http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + if idToken.Expiry.Before(now) { + // token has expired + return false, "", nil, idToken.Expiry, nil + } + username, groups, err = extractInfoFromToken(idToken) + if err != nil { + return false, "", nil, time.Time{}, err + } + groupsIntersect := GroupsIntersect(d.allowedGroups, groups) + if len(groupsIntersect) == 0 { + return true, username, groups, idToken.Expiry, nil + } + return false, username, groups, idToken.Expiry, nil } // clientAssertion adds a JWT client assertion according to diff --git a/pkg/portal/middleware/aad_test.go b/pkg/portal/middleware/aad_test.go index 1aaa3fa8213..07bc3367b9e 100644 --- a/pkg/portal/middleware/aad_test.go +++ b/pkg/portal/middleware/aad_test.go @@ -7,29 +7,25 @@ import ( "context" "crypto/rsa" "crypto/x509" - "encoding/json" - "fmt" + "errors" + "io" "net/http" "net/http/httptest" "net/url" - "reflect" - "strings" "testing" "time" - "github.com/form3tech-oss/jwt-go" - "github.com/go-test/deep" + "github.com/coreos/go-oidc/v3/oidc" + "github.com/golang-jwt/jwt" "github.com/golang/mock/gomock" - "github.com/gorilla/mux" - "github.com/gorilla/securecookie" + "github.com/sirupsen/logrus" "golang.org/x/oauth2" "github.com/Azure/ARO-RP/pkg/util/azureclient" + "github.com/Azure/ARO-RP/pkg/util/cmp" mock_env "github.com/Azure/ARO-RP/pkg/util/mocks/env" - "github.com/Azure/ARO-RP/pkg/util/oidc" "github.com/Azure/ARO-RP/pkg/util/roundtripper" utiltls "github.com/Azure/ARO-RP/pkg/util/tls" - "github.com/Azure/ARO-RP/pkg/util/uuid" testlog "github.com/Azure/ARO-RP/test/util/log" ) @@ -47,776 +43,185 @@ func init() { } } -type noopOauther struct { - tokenMap map[string]interface{} - err error -} - -func (noopOauther) AuthCodeURL(string, ...oauth2.AuthCodeOption) string { - return "authcodeurl" -} - -func (o *noopOauther) Exchange(context.Context, string, ...oauth2.AuthCodeOption) (*oauth2.Token, error) { - if o.err != nil { - return nil, o.err - } - - t := oauth2.Token{} - return t.WithExtra(o.tokenMap), nil -} - func TestNewAAD(t *testing.T) { - _, err := NewAAD(nil, nil, nil, nil, "", nil, "", nil, nil, nil, nil, nil) + _, err := NewAAD(nil, nil, nil, nil, "", nil, "", nil, nil, nil, &oidc.IDTokenVerifier{}) if err.Error() != "invalid sessionKey" { t.Error(err) } } -func TestAAD(t *testing.T) { - for _, tt := range []struct { - name string - request func(*aad) (*http.Request, error) - wantStatusCode int - wantAuthenticated bool - wantUsername string - wantGroups []string - }{ - { - name: "authenticated", - request: func(a *aad) (*http.Request, error) { - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - SessionKeyUsername: "username", - SessionKeyGroups: []string{"group1", "group2"}, - SessionKeyExpires: int64(1), - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - }, nil - }, - wantAuthenticated: true, - wantUsername: "username", - wantGroups: []string{"group1", "group2"}, - }, - { - name: "expired - not authenticated", - request: func(a *aad) (*http.Request, error) { - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - SessionKeyUsername: "username", - SessionKeyGroups: []string{"group1", "group2"}, - SessionKeyExpires: int64(-1), - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - }, nil - }, - }, - { - name: "no cookie - not authenticated", - request: func(a *aad) (*http.Request, error) { - return &http.Request{}, nil - }, - }, - { - name: "invalid cookie", - request: func(a *aad) (*http.Request, error) { - return &http.Request{ - Header: http.Header{ - "Cookie": []string{"session=xxx"}, - }, - URL: &url.URL{Path: ""}, - }, nil - }, - wantStatusCode: http.StatusTemporaryRedirect, - }, - } { - t.Run(tt.name, func(t *testing.T) { - controller := gomock.NewController(t) - defer controller.Finish() - env := mock_env.NewMockInterface(controller) - env.EXPECT().Environment().AnyTimes().Return(&azureclient.PublicCloud) - env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false) - env.EXPECT().TenantID().AnyTimes().Return("common") - - _, audit := testlog.NewAudit() - _, baseLog := testlog.New() - _, baseAccessLog := testlog.New() - a, err := NewAAD(baseLog, audit, env, baseAccessLog, "", make([]byte, 32), "", nil, nil, nil, mux.NewRouter(), nil) - if err != nil { - t.Fatal(err) - } - a.now = func() time.Time { return time.Unix(0, 0) } - - var username string - var usernameok bool - var groups []string - var groupsok bool - h := a.AAD(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - username, usernameok = r.Context().Value(ContextKeyUsername).(string) - groups, groupsok = r.Context().Value(ContextKeyGroups).([]string) - })) - - r, err := tt.request(a) - if err != nil { - t.Fatal(err) - } - - w := httptest.NewRecorder() - - h.ServeHTTP(w, r) - - if tt.wantStatusCode != 0 && w.Code != tt.wantStatusCode { - t.Error(w.Code) - } - - if username != tt.wantUsername { - t.Error(username) - } - if usernameok != tt.wantAuthenticated { - t.Error(usernameok) - } - if !reflect.DeepEqual(groups, tt.wantGroups) { - t.Error(groups) - } - if groupsok != tt.wantAuthenticated { - t.Error(groupsok) - } - }) - } +type fakeAccessValidator struct { + forbidden bool + user string + expiry time.Time + groups []string + err error } -func TestCheckAuthentication(t *testing.T) { - for _, tt := range []struct { - name string - request func(*aad) (*http.Request, error) - wantStatusCode int - wantAuthenticated bool - }{ - { - name: "authenticated", - request: func(a *aad) (*http.Request, error) { - ctx := context.Background() - ctx = context.WithValue(ctx, ContextKeyUsername, "user") - return http.NewRequestWithContext(ctx, http.MethodGet, "/api/info", nil) - }, - wantAuthenticated: true, - wantStatusCode: http.StatusOK, - }, - { - name: "not authenticated", - request: func(a *aad) (*http.Request, error) { - ctx := context.Background() - return http.NewRequestWithContext(ctx, http.MethodGet, "/api/info", nil) - }, - wantStatusCode: http.StatusTemporaryRedirect, - }, - { - name: "not authenticated", - request: func(a *aad) (*http.Request, error) { - ctx := context.Background() - return http.NewRequestWithContext(ctx, http.MethodGet, "/callback", nil) - }, - wantStatusCode: http.StatusTemporaryRedirect, - }, - { - name: "invalid cookie", - request: func(a *aad) (*http.Request, error) { - return &http.Request{ - Header: http.Header{ - "Cookie": []string{"session=xxx"}, - }, - }, nil - }, - wantStatusCode: http.StatusForbidden, - }, - } { - t.Run(tt.name, func(t *testing.T) { - controller := gomock.NewController(t) - defer controller.Finish() - env := mock_env.NewMockInterface(controller) - env.EXPECT().Environment().AnyTimes().Return(&azureclient.PublicCloud) - env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false) - env.EXPECT().TenantID().AnyTimes().Return("common") - - _, audit := testlog.NewAudit() - _, baseLog := testlog.New() - _, baseAccessLog := testlog.New() - a, err := NewAAD(baseLog, audit, env, baseAccessLog, "", make([]byte, 32), "", nil, nil, nil, mux.NewRouter(), nil) - if err != nil { - t.Fatal(err) - } - - var authenticated bool - h := a.CheckAuthentication(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, authenticated = r.Context().Value(ContextKeyUsername).(string) - })) - - r, err := tt.request(a) - if err != nil { - t.Fatal(err) - } - - w := httptest.NewRecorder() - - h.ServeHTTP(w, r) - - if w.Code != tt.wantStatusCode { - t.Error(w.Code, tt.wantStatusCode) - } - - if authenticated != tt.wantAuthenticated { - t.Fatal(authenticated) - } - - if tt.wantStatusCode == http.StatusInternalServerError { - return - } - }) - } +func (f fakeAccessValidator) validateAccess(ctx context.Context, rawIDToken string, now time.Time) (bool, string, []string, time.Time, error) { + return f.forbidden, f.user, f.groups, f.expiry, f.err } -func TestLogin(t *testing.T) { - for _, tt := range []struct { - name string - request func(*aad) (*http.Request, error) - wantStatusCode int +func TestAAD(t *testing.T) { + var tests = []struct { + name string + userReturn string + errReturn error + forbiddenReturn bool + groupsReturn []string + wantUser string + wantGroups []string }{ - { - name: "authenticated", - request: func(a *aad) (*http.Request, error) { - ctx := context.Background() - ctx = context.WithValue(ctx, ContextKeyUsername, "user") - return http.NewRequestWithContext(ctx, http.MethodGet, "/login", nil) - }, - wantStatusCode: http.StatusTemporaryRedirect, - }, - { - name: "not authenticated", - request: func(a *aad) (*http.Request, error) { - ctx := context.Background() - return http.NewRequestWithContext(ctx, http.MethodGet, "/login", nil) - }, - wantStatusCode: http.StatusTemporaryRedirect, - }, - { - name: "invalid cookie", - request: func(a *aad) (*http.Request, error) { - return &http.Request{ - Header: http.Header{ - "Cookie": []string{"session=xxx"}, - }, - }, nil - }, - wantStatusCode: http.StatusInternalServerError, - }, - } { + {name: "authenticated", userReturn: "mattHicks", groupsReturn: []string{"ceo"}, wantUser: "mattHicks", wantGroups: []string{"ceo"}}, + {name: "forbidden", forbiddenReturn: true}, + {name: "error", errReturn: errors.New("expired")}, + } + logger := logrus.New() + logger.SetOutput(io.Discard) + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - controller := gomock.NewController(t) - defer controller.Finish() - env := mock_env.NewMockInterface(controller) - env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false) - env.EXPECT().Environment().AnyTimes().Return(&azureclient.PublicCloud) - env.EXPECT().TenantID().AnyTimes().Return("common") - - _, audit := testlog.NewAudit() - _, baseLog := testlog.New() - _, baseAccessLog := testlog.New() - a, err := NewAAD(baseLog, audit, env, baseAccessLog, "", make([]byte, 32), "", nil, nil, nil, mux.NewRouter(), nil) - if err != nil { - t.Fatal(err) - } - - h := http.HandlerFunc(a.Login) - - r, err := tt.request(a) - if err != nil { - t.Fatal(err) - } - - w := httptest.NewRecorder() - - h.ServeHTTP(w, r) - - if w.Code != tt.wantStatusCode { - t.Error(w.Code, tt.wantStatusCode) - } + aadStruct := &aad{accessValidator: fakeAccessValidator{user: tt.userReturn, groups: tt.groupsReturn, forbidden: tt.forbiddenReturn, err: tt.errReturn}, log: logger.WithContext(context.Background())} + + dummyRequest, _ := http.NewRequest(http.MethodGet, "https://redhat.com/hello", nil) + dummyRequest.AddCookie(&http.Cookie{Name: OIDCCookie, Value: "supertoken"}) + writer := httptest.NewRecorder() + + // this simulates a handler that is called after AAD, to check if we have the right values in the + // context. It will be called from aadStruct.AAD + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user := r.Context().Value(ContextKeyUsername).(string) + if user != tt.wantUser { + t.Errorf("wanted user to be %s but got %s ", tt.wantUser, user) + } + groups := r.Context().Value(ContextKeyGroups).([]string) + if diff := cmp.Diff(groups, tt.wantGroups); diff != "" { + t.Errorf("unexpected groups value %s", diff) + } + }) - if tt.wantStatusCode == http.StatusInternalServerError { - return - } + aadStruct.AAD(nextHandler).ServeHTTP(writer, dummyRequest) - if !strings.HasPrefix(w.Header().Get("Location"), "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=&redirect_uri=https%3A%2F%2F%2Fcallback&response_type=code&scope=openid+profile&state=") { - t.Error(w.Header().Get("Location")) + if tt.forbiddenReturn && writer.Result().StatusCode != http.StatusForbidden { + t.Errorf("was expecting status code to be 403 but got %d", writer.Result().StatusCode) + } else if tt.errReturn != nil && writer.Result().StatusCode != http.StatusTemporaryRedirect { + t.Errorf("was expecting status code to be 302 but got %d", writer.Result().StatusCode) } }) } } func TestLogout(t *testing.T) { - for _, tt := range []struct { - name string - request func(*aad) (*http.Request, error) - wantStatusCode int - }{ - { - name: "authenticated", - request: func(a *aad) (*http.Request, error) { - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - SessionKeyUsername: "username", - SessionKeyGroups: []string{"group1", "group2"}, - SessionKeyExpires: int64(0), - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - URL: &url.URL{}, - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - }, nil - }, - wantStatusCode: http.StatusSeeOther, - }, - { - name: "no cookie - not authenticated", - request: func(a *aad) (*http.Request, error) { - return &http.Request{ - URL: &url.URL{}, - }, nil - }, - wantStatusCode: http.StatusSeeOther, - }, - { - name: "invalid cookie", - request: func(a *aad) (*http.Request, error) { - return &http.Request{ - Header: http.Header{ - "Cookie": []string{"session=xxx"}, - }, - }, nil - }, - wantStatusCode: http.StatusInternalServerError, - }, - } { - t.Run(tt.name, func(t *testing.T) { - controller := gomock.NewController(t) - defer controller.Finish() - env := mock_env.NewMockInterface(controller) - env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false) - env.EXPECT().Environment().AnyTimes().Return(&azureclient.PublicCloud) - env.EXPECT().TenantID().AnyTimes().Return("common") - - _, audit := testlog.NewAudit() - _, baseLog := testlog.New() - _, baseAccessLog := testlog.New() - a, err := NewAAD(baseLog, audit, env, baseAccessLog, "", make([]byte, 32), "", nil, nil, nil, mux.NewRouter(), nil) - if err != nil { - t.Fatal(err) - } - - h := a.Logout("/bye") - - r, err := tt.request(a) - if err != nil { - t.Fatal(err) - } - - w := httptest.NewRecorder() + request, err := http.NewRequest(http.MethodGet, "https://redhat.com", nil) + if err != nil { + t.Fatal("could not create the request", err) + } + request.AddCookie(&http.Cookie{ + Name: OIDCCookie, + Value: "I love potatoes", + }) - h.ServeHTTP(w, r) + writer := httptest.NewRecorder() - if w.Code != tt.wantStatusCode { - t.Error(w.Code) - } + aadStruct := &aad{} + aadStruct.Logout("/").ServeHTTP(writer, request) - if tt.wantStatusCode == http.StatusInternalServerError { - return + found := false + for _, v := range writer.Result().Cookies() { + if v.Name == OIDCCookie { + found = true + if v.MaxAge != -1 { + t.Error("cookie does not have expected max age field") } + } + } - if w.Header().Get("Location") != "/bye" { - t.Error(w.Header().Get("Location")) - } + if !found { + t.Error("cookie was not found") + } +} - var m map[interface{}]interface{} - cookies := w.Result().Cookies() - err = securecookie.DecodeMulti(SessionName, cookies[len(cookies)-1].Value, &m, a.store.Codecs...) - if err != nil { - t.Fatal(err) - } +type noopOauther struct { + tokenMap map[string]interface{} + err error +} - if len(m) != 0 { - t.Error(len(m)) - } - }) - } +func (noopOauther) AuthCodeURL(string, ...oauth2.AuthCodeOption) string { + return "authcodeurl" } -func TestCallback(t *testing.T) { - clientID := "00000000-0000-0000-0000-000000000000" - groups := []string{ - "00000000-0000-0000-0000-000000000001", +func (o *noopOauther) Exchange(context.Context, string, ...oauth2.AuthCodeOption) (*oauth2.Token, error) { + if o.err != nil { + return nil, o.err } - username := "user" - idToken, err := json.Marshal(claims{ - Groups: groups, - PreferredUsername: username, - }) - if err != nil { - t.Fatal(err) - } + t := oauth2.Token{} + return t.WithExtra(o.tokenMap), nil +} - for _, tt := range []struct { - name string - request func(*aad) (*http.Request, error) - oauther oauther - verifier oidc.Verifier - wantAuthenticated bool - wantError string - wantForbidden bool +func TestCallback(t *testing.T) { + var tests = []struct { + name string + wantStatusCode int + wantLocation string + exchangeError error + token string + validatorErr error + validatorExpiry time.Time + hasStateCookie bool + stateMismatch bool + wantOIDCCookie bool }{ - { - name: "success", - request: func(a *aad) (*http.Request, error) { - uuid := uuid.DefaultGenerator.Generate() - - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - sessionKeyState: uuid, - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - URL: &url.URL{}, - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - Form: url.Values{ - "state": []string{uuid}, - }, - }, nil - }, - oauther: &noopOauther{ - tokenMap: map[string]interface{}{ - "id_token": string(idToken), - }, - }, - verifier: &oidc.NoopVerifier{}, - wantAuthenticated: true, - }, - { - name: "fail - invalid cookie", - request: func(a *aad) (*http.Request, error) { - return &http.Request{ - Header: http.Header{ - "Cookie": []string{"session=xxx"}, - }, - }, nil - }, - wantError: "Internal Server Error\n", - }, - { - name: "fail - corrupt sessionKeyState", - request: func(a *aad) (*http.Request, error) { - return &http.Request{ - URL: &url.URL{}, - }, nil - }, - oauther: &noopOauther{}, - }, - { - name: "fail - state mismatch", - request: func(a *aad) (*http.Request, error) { - uuid := uuid.DefaultGenerator.Generate() - - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - sessionKeyState: uuid, - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - URL: &url.URL{}, - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - Form: url.Values{ - "state": []string{"bad"}, - }, - }, nil - }, - wantError: "Internal Server Error\n", - }, - { - name: "fail - error returned", - request: func(a *aad) (*http.Request, error) { - uuid := uuid.DefaultGenerator.Generate() - - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - sessionKeyState: uuid, - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - URL: &url.URL{}, - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - Form: url.Values{ - "state": []string{uuid}, - "error": []string{"bad things happened."}, - "error_description": []string{"really bad things."}, - }, - }, nil - }, - wantError: "Internal Server Error\n", - }, - { - name: "fail - oauther failed", - request: func(a *aad) (*http.Request, error) { - uuid := uuid.DefaultGenerator.Generate() - - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - sessionKeyState: uuid, - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - URL: &url.URL{}, - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - Form: url.Values{ - "state": []string{uuid}, - }, - }, nil - }, - oauther: &noopOauther{ - err: fmt.Errorf("failed"), - }, - wantError: "Internal Server Error\n", - }, - { - name: "fail - no idtoken", - request: func(a *aad) (*http.Request, error) { - uuid := uuid.DefaultGenerator.Generate() - - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - sessionKeyState: uuid, - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - URL: &url.URL{}, - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - Form: url.Values{ - "state": []string{uuid}, - }, - }, nil - }, - oauther: &noopOauther{}, - wantError: "Internal Server Error\n", - }, - { - name: "fail - verifier error", - request: func(a *aad) (*http.Request, error) { - uuid := uuid.DefaultGenerator.Generate() - - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - sessionKeyState: uuid, - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - URL: &url.URL{}, - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - Form: url.Values{ - "state": []string{uuid}, - }, - }, nil - }, - oauther: &noopOauther{ - tokenMap: map[string]interface{}{"id_token": ""}, - }, - verifier: &oidc.NoopVerifier{ - Err: fmt.Errorf("failed"), - }, - wantError: "Internal Server Error\n", - }, - { - name: "fail - invalid claims", - request: func(a *aad) (*http.Request, error) { - uuid := uuid.DefaultGenerator.Generate() - - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - sessionKeyState: uuid, - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - URL: &url.URL{}, - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - Form: url.Values{ - "state": []string{uuid}, - }, - }, nil - }, - oauther: &noopOauther{ - tokenMap: map[string]interface{}{ - "id_token": "", - }, - }, - verifier: &oidc.NoopVerifier{}, - wantError: "Internal Server Error\n", - }, - { - name: "fail - group mismatch", - request: func(a *aad) (*http.Request, error) { - uuid := uuid.DefaultGenerator.Generate() - - cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{ - sessionKeyState: uuid, - }, a.store.Codecs...) - if err != nil { - return nil, err - } - - return &http.Request{ - URL: &url.URL{}, - Header: http.Header{ - "Cookie": []string{SessionName + "=" + cookie}, - }, - Form: url.Values{ - "state": []string{uuid}, - }, - }, nil - }, - oauther: &noopOauther{ - tokenMap: map[string]interface{}{ - "id_token": "null", - }, - }, - verifier: &oidc.NoopVerifier{}, - wantForbidden: true, - }, - } { + {name: "no state cookie", hasStateCookie: false, wantStatusCode: http.StatusTemporaryRedirect, wantLocation: "/api/login"}, + {name: "state mismatch", stateMismatch: true, hasStateCookie: true, wantStatusCode: http.StatusInternalServerError}, + {name: "exchange error", hasStateCookie: true, wantStatusCode: http.StatusInternalServerError, exchangeError: errors.New("err")}, + {name: "no id token", hasStateCookie: true, wantStatusCode: http.StatusInternalServerError}, + {name: "validator error", hasStateCookie: true, wantStatusCode: http.StatusTemporaryRedirect, wantLocation: "/api/login", token: "supertoken", validatorErr: errors.New("validator error")}, + {name: "all good", hasStateCookie: true, wantStatusCode: http.StatusTemporaryRedirect, wantLocation: "/", token: "supertoken", wantOIDCCookie: true}, + } + for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { - controller := gomock.NewController(t) - defer controller.Finish() - env := mock_env.NewMockInterface(controller) - env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false) - env.EXPECT().Environment().AnyTimes().Return(&azureclient.PublicCloud) - env.EXPECT().TenantID().AnyTimes().Return("common") - - _, audit := testlog.NewAudit() - _, baseLog := testlog.New() - _, baseAccessLog := testlog.New() - a, err := NewAAD(baseLog, audit, env, baseAccessLog, "", make([]byte, 32), clientID, clientkey, clientcerts, groups, mux.NewRouter(), tt.verifier) - if err != nil { - t.Fatal(err) - } - a.now = func() time.Time { return time.Unix(0, 0) } - a.oauther = tt.oauther + logger := logrus.New() + logger.Out = io.Discard + aad := &aad{log: logrus.NewEntry(logger), now: time.Now} - r, err := tt.request(a) - if err != nil { - t.Fatal(err) + tokenMap := make(map[string]interface{}) + if tt.token != "" { + tokenMap["id_token"] = tt.token } + aad.oauther = &noopOauther{err: tt.exchangeError, tokenMap: tokenMap} + aad.accessValidator = fakeAccessValidator{err: tt.validatorErr, expiry: tt.validatorExpiry} - w := httptest.NewRecorder() - - a.callback(w, r) - - if tt.wantError != "" { - if w.Code != http.StatusInternalServerError { - t.Error(w.Code) - } - - if w.Body.String() != tt.wantError { - t.Error(w.Body.String()) - } - - return + writer := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/callback", nil) + if tt.hasStateCookie { + request.AddCookie(&http.Cookie{Name: stateCookie, Value: "some value"}) } - type cookie map[interface{}]interface{} - var m cookie - cookies := w.Result().Cookies() - err = securecookie.DecodeMulti(SessionName, cookies[len(cookies)-1].Value, &m, a.store.Codecs...) - if err != nil { - t.Fatal(err) + request.Form = map[string][]string{"state": {"some value"}} + if tt.stateMismatch { + request.Form = map[string][]string{"state": {"some other value"}} } - switch { - case tt.wantAuthenticated: - if w.Code != http.StatusTemporaryRedirect { - t.Error(w.Code) - } - - if w.Header().Get("Location") != "/" { - t.Error(w.Header().Get("Location")) - } - - for _, l := range deep.Equal(m, cookie{ - SessionKeyExpires: int64(3600), - SessionKeyGroups: groups, - SessionKeyUsername: username, - }) { - t.Error(l) - } - - case tt.wantForbidden: - if w.Code != http.StatusForbidden { - t.Error(w.Code) - } - - if w.Header().Get("Location") != "/" { - t.Error(w.Header().Get("Location")) - } + aad.Callback(writer, request) - for _, l := range deep.Equal(m, cookie{}) { - t.Error(l) - } - default: - if w.Code != http.StatusTemporaryRedirect { - t.Error(w.Code) + if writer.Result().StatusCode != tt.wantStatusCode { + t.Errorf("wanted status code to be %d but got %d", tt.wantStatusCode, writer.Result().StatusCode) + } + if tt.wantLocation != writer.Result().Header.Get("Location") { + t.Errorf("wanted location header to be %s but got %s", tt.wantLocation, writer.Result().Header.Get("Location")) + } + if tt.wantOIDCCookie { + hasCookie := false + for _, v := range writer.Result().Cookies() { + if v.Name == OIDCCookie && v.Value == tt.token && v.SameSite == http.SameSiteStrictMode && v.Secure == true && v.HttpOnly == true { + hasCookie = true + break + } } - - if w.Header().Get("Location") != "/authcodeurl" { - t.Error(w.Header().Get("Location")) + if !hasCookie { + t.Error("did not have the right cookie") } - return } }) } @@ -834,7 +239,7 @@ func TestClientAssertion(t *testing.T) { _, audit := testlog.NewAudit() _, baseLog := testlog.New() _, baseAccessLog := testlog.New() - a, err := NewAAD(baseLog, audit, env, baseAccessLog, "", make([]byte, 32), clientID, clientkey, clientcerts, nil, mux.NewRouter(), nil) + a, err := NewAAD(baseLog, audit, env, baseAccessLog, "", make([]byte, 32), clientID, clientkey, clientcerts, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/portal/middleware/intAAD.go b/pkg/portal/middleware/intAAD.go new file mode 100644 index 00000000000..6fad93f23a6 --- /dev/null +++ b/pkg/portal/middleware/intAAD.go @@ -0,0 +1,55 @@ +package middleware + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "net/http" + + "github.com/sirupsen/logrus" +) + +const ( + IntUsernameKey = "INT_OAUTH_USERNAME" + IntGroupsKey = "INT_OAUTH_GROUPS" + IntPasswordKey = "INT_PASSWORD" +) + +// IntAAD effectively disable authentication for testing purposes +type IntAAD struct { + log *logrus.Entry + elevatedGroups []string +} + +func NewIntAAD(groups []string, log *logrus.Entry) (IntAAD, error) { + return IntAAD{ + elevatedGroups: groups, + log: log, + }, nil +} + +func (a IntAAD) Callback(w http.ResponseWriter, r *http.Request) { +} + +func (a IntAAD) Login(w http.ResponseWriter, r *http.Request) { +} + +func (a IntAAD) AAD(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + a.log.Infof("running AAD middleware from int") + a.log.Infof("there are %d cookies", len(r.Cookies())) + + ctx := r.Context() + ctx = context.WithValue(ctx, ContextKeyUsername, "test") + ctx = context.WithValue(ctx, ContextKeyGroups, a.elevatedGroups) + r = r.WithContext(ctx) + + h.ServeHTTP(w, r) + }) +} + +func (a IntAAD) Logout(url string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + }) +} diff --git a/pkg/portal/portal.go b/pkg/portal/portal.go index 33fe9573e34..4e6eb87d8c4 100644 --- a/pkg/portal/portal.go +++ b/pkg/portal/portal.go @@ -15,10 +15,12 @@ import ( "log" "net" "net/http" + "os" "regexp" "strings" "time" + "github.com/coreos/go-oidc/v3/oidc" "github.com/gorilla/csrf" "github.com/gorilla/mux" "github.com/sirupsen/logrus" @@ -36,7 +38,6 @@ import ( "github.com/Azure/ARO-RP/pkg/portal/ssh" "github.com/Azure/ARO-RP/pkg/proxy" "github.com/Azure/ARO-RP/pkg/util/heartbeat" - "github.com/Azure/ARO-RP/pkg/util/oidc" ) type Runnable interface { @@ -50,7 +51,7 @@ type portal struct { baseAccessLog *logrus.Entry l net.Listener sshl net.Listener - verifier oidc.Verifier + verifier *oidc.IDTokenVerifier hostname string servingKey *rsa.PrivateKey @@ -83,7 +84,7 @@ func NewPortal(env env.Core, baseAccessLog *logrus.Entry, l net.Listener, sshl net.Listener, - verifier oidc.Verifier, + verifier *oidc.IDTokenVerifier, hostname string, servingKey *rsa.PrivateKey, servingCerts []*x509.Certificate, @@ -160,7 +161,16 @@ func (p *portal) setupRouter(kconfig *kubeconfig.Kubeconfig, prom *prometheus.Pr allGroups := append([]string{}, p.groupIDs...) allGroups = append(allGroups, p.elevatedGroupIDs...) - p.aad, err = middleware.NewAAD(p.log, p.audit, p.env, p.baseAccessLog, p.hostname, p.sessionKey, p.clientID, p.clientKey, p.clientCerts, allGroups, unauthenticatedRouter, p.verifier) + // we get the env var from within the function because it is not meant to be configurable + disableOauthOption := os.Getenv("DISABLE_OAUTH") + if (disableOauthOption == "true" && env.IsCI()) || env.IsLocalDevelopmentMode() { + p.log.Infof("running in int, disableoauth=%s", disableOauthOption) + p.aad, err = middleware.NewIntAAD(p.elevatedGroupIDs, p.audit) + } else { + p.aad, err = middleware.NewAAD(p.log, p.audit, p.env, p.baseAccessLog, p.hostname, p.sessionKey, p.clientID, p.clientKey, p.clientCerts, allGroups, + p.verifier) + } + p.aadRoutes(unauthenticatedRouter) if err != nil { return nil, err } @@ -168,14 +178,18 @@ func (p *portal) setupRouter(kconfig *kubeconfig.Kubeconfig, prom *prometheus.Pr aadAuthenticatedRouter := r.NewRoute().Subrouter() aadAuthenticatedRouter.Use(p.aad.AAD) aadAuthenticatedRouter.Use(middleware.Log(p.env, p.audit, p.baseAccessLog)) - aadAuthenticatedRouter.Use(p.aad.CheckAuthentication) - aadAuthenticatedRouter.Use(csrf.Protect(p.sessionKey, csrf.SameSite(csrf.SameSiteStrictMode), csrf.MaxAge(0), csrf.Path("/"))) p.aadAuthenticatedRoutes(aadAuthenticatedRouter, prom, kconfig, sshStruct) return r, nil } +func (p *portal) aadRoutes(r *mux.Router) { + r.Methods(http.MethodGet).Path("/callback").Handler(middleware.Log(p.env, p.audit, p.baseAccessLog)(http.HandlerFunc(p.aad.Callback))) + r.Methods(http.MethodGet).Path("/api/login").Handler(middleware.Log(p.env, p.audit, p.baseAccessLog)(http.HandlerFunc(p.aad.Login))) + r.Methods(http.MethodPost).Path("/api/logout").Handler(middleware.Log(p.env, p.audit, p.baseAccessLog)(p.aad.Logout("/"))) +} + func (p *portal) setupServices() (*kubeconfig.Kubeconfig, *prometheus.Prometheus, *ssh.SSH, error) { ssh, err := ssh.New(p.env, p.log, p.baseAccessLog, p.sshl, p.sshKey, p.elevatedGroupIDs, p.dbOpenShiftClusters, p.dbPortal, p.dialer) if err != nil { diff --git a/pkg/portal/security_test.go b/pkg/portal/security_test.go deleted file mode 100644 index 2c84547feae..00000000000 --- a/pkg/portal/security_test.go +++ /dev/null @@ -1,458 +0,0 @@ -package portal - -// Copyright (c) Microsoft Corporation. -// Licensed under the Apache License 2.0. - -import ( - "context" - "crypto/tls" - "crypto/x509" - "encoding/base64" - "encoding/json" - "fmt" - "net/http" - "strings" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/gorilla/securecookie" - "github.com/gorilla/sessions" - "github.com/sirupsen/logrus" - "k8s.io/utils/strings/slices" - - "github.com/Azure/ARO-RP/pkg/metrics/noop" - "github.com/Azure/ARO-RP/pkg/portal/middleware" - "github.com/Azure/ARO-RP/pkg/util/azureclient" - "github.com/Azure/ARO-RP/pkg/util/log/audit" - mock_env "github.com/Azure/ARO-RP/pkg/util/mocks/env" - utiltls "github.com/Azure/ARO-RP/pkg/util/tls" - testdatabase "github.com/Azure/ARO-RP/test/database" - "github.com/Azure/ARO-RP/test/util/listener" - testlog "github.com/Azure/ARO-RP/test/util/log" - "github.com/Azure/ARO-RP/test/util/testpoller" -) - -var ( - nonElevatedGroupIDs = []string{"00000000-1111-1111-1111-000000000000"} - elevatedGroupIDs = []string{"00000000-0000-0000-0000-000000000000"} -) - -func TestSecurity(t *testing.T) { - ctx := context.Background() - log := logrus.NewEntry(logrus.StandardLogger()) - - _, portalAccessLog := testlog.New() - _, portalLog := testlog.New() - auditHook, portalAuditLog := testlog.NewAudit() - - controller := gomock.NewController(t) - defer controller.Finish() - - _env := mock_env.NewMockCore(controller) - _env.EXPECT().IsLocalDevelopmentMode().AnyTimes().Return(false) - _env.EXPECT().Location().AnyTimes().Return("eastus") - _env.EXPECT().TenantID().AnyTimes().Return("00000000-0000-0000-0000-000000000001") - _env.EXPECT().Environment().AnyTimes().Return(&azureclient.PublicCloud) - _env.EXPECT().Hostname().AnyTimes().Return("testhost") - - l := listener.NewListener() - defer l.Close() - - sshl := listener.NewListener() - defer sshl.Close() - - serverkey, servercerts, err := utiltls.GenerateKeyAndCertificate("server", nil, nil, false, false) - if err != nil { - t.Fatal(err) - } - - sshkey, _, err := utiltls.GenerateKeyAndCertificate("ssh", nil, nil, false, false) - if err != nil { - t.Fatal(err) - } - - dbOpenShiftClusters, _ := testdatabase.NewFakeOpenShiftClusters() - dbPortal, _ := testdatabase.NewFakePortal() - - pool := x509.NewCertPool() - pool.AddCert(servercerts[0]) - - c := &http.Client{ - Transport: &http.Transport{ - DialContext: l.DialContext, - TLSClientConfig: &tls.Config{ - RootCAs: pool, - }, - }, - CheckRedirect: func(*http.Request, []*http.Request) error { - return http.ErrUseLastResponse - }, - } - - p := NewPortal(_env, portalAuditLog, portalLog, portalAccessLog, l, sshl, nil, "", serverkey, servercerts, "", nil, nil, make([]byte, 32), sshkey, nil, elevatedGroupIDs, dbOpenShiftClusters, dbPortal, nil, &noop.Noop{}) - go func() { - err := p.Run(ctx) - if err != nil { - log.Error(err) - } - }() - - for _, tt := range []struct { - name string - request func() (*http.Request, error) - checkResponse func(*testing.T, bool, bool, *http.Response) - unauthenticatedWantStatusCode int - authenticatedWantStatusCode int - wantAuditOperation string - wantAuditTargetResources []audit.TargetResource - }{ - { - name: "/", - request: func() (*http.Request, error) { - return http.NewRequest(http.MethodGet, "https://server/", nil) - }, - unauthenticatedWantStatusCode: 307, - authenticatedWantStatusCode: 200, - wantAuditOperation: "GET /", - wantAuditTargetResources: []audit.TargetResource{ - { - TargetResourceType: "", - TargetResourceName: "/", - }, - }, - }, - { - name: "/main.js", - request: func() (*http.Request, error) { - return http.NewRequest(http.MethodGet, "https://server/main.js", nil) - }, - wantAuditOperation: "GET /main.js", - wantAuditTargetResources: []audit.TargetResource{ - { - TargetResourceType: "", - TargetResourceName: "/main.js", - }, - }, - }, - { - name: "/api/clusters", - request: func() (*http.Request, error) { - return http.NewRequest(http.MethodGet, "https://server/api/clusters", nil) - }, - wantAuditOperation: "GET /api/clusters", - wantAuditTargetResources: []audit.TargetResource{ - { - TargetResourceType: "", - TargetResourceName: "/api/clusters", - }, - }, - }, - { - name: "/api/logout", - request: func() (*http.Request, error) { - return http.NewRequest(http.MethodPost, "https://server/api/logout", nil) - }, - unauthenticatedWantStatusCode: http.StatusSeeOther, - authenticatedWantStatusCode: http.StatusSeeOther, - wantAuditOperation: "POST /api/logout", - wantAuditTargetResources: []audit.TargetResource{ - { - TargetResourceType: "", - TargetResourceName: "/api/logout", - }, - }, - }, - { - name: "/callback", - request: func() (*http.Request, error) { - return http.NewRequest(http.MethodGet, "https://server/callback", nil) - }, - unauthenticatedWantStatusCode: http.StatusTemporaryRedirect, - authenticatedWantStatusCode: http.StatusTemporaryRedirect, - wantAuditOperation: "GET /callback", - wantAuditTargetResources: []audit.TargetResource{ - { - TargetResourceType: "", - TargetResourceName: "/callback", - }, - }, - }, - { - name: "/healthz/ready", - request: func() (*http.Request, error) { - return http.NewRequest(http.MethodGet, "https://server/healthz/ready", nil) - }, - unauthenticatedWantStatusCode: http.StatusOK, - wantAuditOperation: "GET /healthz/ready", - wantAuditTargetResources: []audit.TargetResource{ - { - TargetResourceType: "", - TargetResourceName: "/healthz/ready", - }, - }, - }, - { - name: "/kubeconfig/new", - request: func() (*http.Request, error) { - return http.NewRequest(http.MethodPost, "https://server/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroupName/providers/microsoft.redhatopenshift/openshiftclusters/resourceName/kubeconfig/new", nil) - }, - wantAuditOperation: "POST /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourcegroupname/providers/microsoft.redhatopenshift/openshiftclusters/resourcename/kubeconfig/new", - wantAuditTargetResources: []audit.TargetResource{ - { - TargetResourceType: "kubeconfig", - TargetResourceName: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourcegroupname/providers/microsoft.redhatopenshift/openshiftclusters/resourcename/kubeconfig/new", - }, - }, - }, - { - name: "/prometheus", - request: func() (*http.Request, error) { - return http.NewRequest(http.MethodPost, "https://server/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroupName/providers/microsoft.redhatopenshift/openshiftclusters/resourceName/prometheus", nil) - }, - authenticatedWantStatusCode: http.StatusTemporaryRedirect, - wantAuditOperation: "POST /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourcegroupname/providers/microsoft.redhatopenshift/openshiftclusters/resourcename/prometheus", - wantAuditTargetResources: []audit.TargetResource{ - { - TargetResourceType: "prometheus", - TargetResourceName: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourcegroupname/providers/microsoft.redhatopenshift/openshiftclusters/resourcename/prometheus", - }, - }, - }, - { - name: "/ssh/new", - request: func() (*http.Request, error) { - req, err := http.NewRequest(http.MethodPost, "https://server/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroupName/providers/microsoft.redhatopenshift/openshiftclusters/resourceName/ssh/new", strings.NewReader("{}")) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/json") - - return req, nil - }, - checkResponse: func(t *testing.T, authenticated, elevated bool, resp *http.Response) { - if authenticated && !elevated { - var e struct { - Error string - } - err := json.NewDecoder(resp.Body).Decode(&e) - if err != nil { - t.Fatal(err) - } - if e.Error != "Elevated access is required." { - t.Error(e.Error) - } - } - }, - wantAuditOperation: "POST /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourcegroupname/providers/microsoft.redhatopenshift/openshiftclusters/resourcename/ssh/new", - wantAuditTargetResources: []audit.TargetResource{ - { - TargetResourceType: "ssh", - TargetResourceName: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourcegroupname/providers/microsoft.redhatopenshift/openshiftclusters/resourcename/ssh/new", - }, - }, - }, - { - name: "/doesnotexist", - request: func() (*http.Request, error) { - return http.NewRequest(http.MethodGet, "https://server/doesnotexist", nil) - }, - unauthenticatedWantStatusCode: http.StatusNotFound, - authenticatedWantStatusCode: http.StatusNotFound, - }, - } { - for _, tt2 := range []struct { - name string - authenticated bool - elevated bool - wantStatusCode int - }{ - { - name: "unauthenticated", - wantStatusCode: tt.unauthenticatedWantStatusCode, - }, - { - name: "authenticated", - authenticated: true, - wantStatusCode: tt.authenticatedWantStatusCode, - }, - { - name: "elevated", - authenticated: true, - elevated: true, - wantStatusCode: tt.authenticatedWantStatusCode, - }, - } { - t.Run(tt2.name+tt.name, func(t *testing.T) { - defer auditHook.Reset() - - req, err := tt.request() - if err != nil { - t.Fatal(err) - } - - err = addCSRF(req) - if err != nil { - t.Fatal(err) - } - - if tt2.authenticated { - var groups []string - if tt2.elevated { - groups = elevatedGroupIDs - } - err = addAuth(req, groups) - if err != nil { - t.Fatal(err) - } - } - - resp, err := c.Do(req) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - - if tt2.wantStatusCode == 0 { - if tt2.authenticated { - tt2.wantStatusCode = http.StatusOK - } else { - tt2.wantStatusCode = http.StatusTemporaryRedirect - } - } - - if resp.StatusCode != tt2.wantStatusCode { - t.Error(resp.StatusCode, tt2.wantStatusCode) - body := make([]byte, 0) - _, err := resp.Body.Read(body) - if err != nil { - t.Fatal(err) - } - t.Error(body) - } - - if tt.checkResponse != nil { - tt.checkResponse(t, tt2.authenticated, tt2.elevated, resp) - } - - // no audit logs for https://server/doesnotexist - if tt.authenticatedWantStatusCode == http.StatusNotFound { - return - } - - // perform some polling on static files because the http.ServeContent() calls in the - // portal's serve() and index() handlers[1] issued a call to io.Copy()[2] - // causes a race condition with the audit hook. The response was returned - // to the client and the testlog.AssertAuditPayloads() was called immediately, - // while the audit hook was still in-flight. - // - // note that the audit logs will still be recorded and emitted by the audit - // hook, so this is a non-issue in the Geneva environment. - // - // [1] https://github.com/Azure/ARO-RP/blob/master/pkg/portal/portal.go#L222-L247 - // [2] https://go.googlesource.com/go/+/go1.16.2/src/net/http/fs.go#337 - // - // TODO: there is a data race that exists only within this test independent of the polling - // race mentioned above. AllEntries returns a copy of the current entries within logrus, - // but the underlying data within the entry is not copied over. When we attempt to - // get the entry in the Data map for the MetadataPayload, there is a slight chance that - // the Payload will change during this access, resulting in the e2e panicking. - // `go test -race -timeout 30s -run ^TestSecurity$ ./pkg/portal` should show the race and - // where the concurrent read/write is occurring. - if tt.name == "/" || tt.name == "/main.js" { - err = testpoller.Poll(1*time.Second, 5*time.Millisecond, func() (bool, error) { - if len(auditHook.AllEntries()) == 1 { - if _, ok := auditHook.AllEntries()[0].Data[audit.MetadataPayload]; ok { - return true, nil - } - } - return false, nil - }) - if err != nil { - t.Error(err) - } - } - - if tt.wantAuditOperation != "" { - payload := auditPayloadFixture() - payload.OperationName = tt.wantAuditOperation - payload.TargetResources = tt.wantAuditTargetResources - payload.Result.ResultDescription = fmt.Sprintf("Status code: %d", tt2.wantStatusCode) - - if tt2.wantStatusCode == http.StatusForbidden { - payload.Result.ResultType = audit.ResultTypeFail - } - - if tt2.authenticated && !slices.Contains([]string{ - "/callback", "/healthz/ready", "/api/login", "/api/logout"}, tt.name) { - payload.CallerIdentities[0].CallerIdentityValue = "username" - } - testlog.AssertAuditPayloads(t, auditHook, []*audit.Payload{payload}) - } else { - testlog.AssertAuditPayloads(t, auditHook, []*audit.Payload{}) - } - }) - } - } -} - -func addCSRF(req *http.Request) error { - if req.Method != http.MethodPost { - return nil - } - - req.Header.Set("X-CSRF-Token", base64.StdEncoding.EncodeToString(make([]byte, 64))) - - sc := securecookie.New(make([]byte, 32), nil) - sc.SetSerializer(securecookie.JSONEncoder{}) - - cookie, err := sc.Encode("_gorilla_csrf", make([]byte, 32)) - if err != nil { - return err - } - req.Header.Add("Cookie", "_gorilla_csrf="+cookie) - - return nil -} - -func addAuth(req *http.Request, groups []string) error { - store := sessions.NewCookieStore(make([]byte, 32)) - - cookie, err := securecookie.EncodeMulti(middleware.SessionName, map[interface{}]interface{}{ - middleware.SessionKeyUsername: "username", - middleware.SessionKeyGroups: groups, - middleware.SessionKeyExpires: time.Now().Add(time.Hour).Unix(), - }, store.Codecs...) - if err != nil { - return err - } - req.Header.Add("Cookie", middleware.SessionName+"="+cookie) - - return nil -} - -func auditPayloadFixture() *audit.Payload { - return &audit.Payload{ - EnvVer: audit.IFXAuditVersion, - EnvName: audit.IFXAuditName, - EnvFlags: 257, - EnvAppID: audit.SourceAdminPortal, - EnvCloudName: azureclient.PublicCloud.Name, - EnvCloudRole: audit.CloudRoleRP, - EnvCloudRoleInstance: "testhost", - EnvCloudEnvironment: azureclient.PublicCloud.Name, - EnvCloudLocation: "eastus", - EnvCloudVer: 1, - CallerIdentities: []audit.CallerIdentity{ - { - CallerDisplayName: "", - CallerIdentityType: audit.CallerIdentityTypeUsername, - CallerIPAddress: "bufferedpipe", - }, - }, - Category: audit.CategoryResourceManagement, - Result: audit.Result{ - ResultType: audit.ResultTypeSuccess, - }, - } -} diff --git a/pkg/portal/ssh/ssh.go b/pkg/portal/ssh/ssh.go index 6977f7332e5..9661af89848 100644 --- a/pkg/portal/ssh/ssh.go +++ b/pkg/portal/ssh/ssh.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto/rsa" "encoding/json" + "errors" "fmt" "mime" "net" @@ -132,7 +133,12 @@ func (s *SSH) New(w http.ResponseWriter, r *http.Request) { return } - elevated := len(middleware.GroupsIntersect(s.elevatedGroupIDs, ctx.Value(middleware.ContextKeyGroups).([]string))) > 0 + groups, ok := ctx.Value(middleware.ContextKeyGroups).([]string) + if !ok { + s.internalServerError(w, errors.New("could not find any groups")) + return + } + elevated := len(middleware.GroupsIntersect(s.elevatedGroupIDs, groups)) > 0 if !elevated { s.sendResponse(w, "", "", "", "Elevated access is required.", s.env.IsLocalDevelopmentMode()) return diff --git a/pkg/util/oidc/oidc.go b/pkg/util/oidc/oidc.go index 5efbef1d66f..5b2c82b2aa3 100644 --- a/pkg/util/oidc/oidc.go +++ b/pkg/util/oidc/oidc.go @@ -7,7 +7,7 @@ import ( "context" "encoding/json" - "github.com/coreos/go-oidc" + "github.com/coreos/go-oidc/v3/oidc" ) type Verifier interface { diff --git a/test/e2e/setup.go b/test/e2e/setup.go index 159bac5bdab..1e0a176d8b0 100644 --- a/test/e2e/setup.go +++ b/test/e2e/setup.go @@ -6,10 +6,8 @@ package e2e import ( "context" "fmt" - "math" "net/url" "os" - "os/exec" "path/filepath" "regexp" "time" @@ -196,37 +194,6 @@ func adminPortalSessionSetup() (string, *selenium.WebDriver) { log.Infof("Could not get to %s. With error : %s", host, err.Error()) } - var portalAuthCmd string - var portalAuthArgs = make([]string, 0) - if os.Getenv("CI") != "" { - // In CI we have a prebuilt portalauth binary - portalAuthCmd = "./portalauth" - } else { - portalAuthCmd = "go" - portalAuthArgs = []string{"run", "./hack/portalauth"} - } - - portalAuthArgs = append(portalAuthArgs, "-username", "test", "-groups", "$AZURE_PORTAL_ELEVATED_GROUP_IDS") - - cmd := exec.Command(portalAuthCmd, portalAuthArgs...) - output, err := cmd.Output() - if err != nil { - log.Fatalf("Error occurred creating session cookie\n Output: %s\n Error: %s\n", output, err) - } - - os.Setenv("SESSION", string(output)) - - log.Infof("Session Output : %s\n", os.Getenv("SESSION")) - - cookie := &selenium.Cookie{ - Name: "session", - Value: os.Getenv("SESSION"), - Expiry: math.MaxUint32, - } - - if err := wd.AddCookie(cookie); err != nil { - panic(err) - } return host, &wd } diff --git a/vendor/github.com/coreos/go-oidc/.gitignore b/vendor/github.com/coreos/go-oidc/.gitignore deleted file mode 100644 index c96f2f47bc6..00000000000 --- a/vendor/github.com/coreos/go-oidc/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/bin -/gopath diff --git a/vendor/github.com/coreos/go-oidc/.travis.yml b/vendor/github.com/coreos/go-oidc/.travis.yml deleted file mode 100644 index 3fddaaac90e..00000000000 --- a/vendor/github.com/coreos/go-oidc/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: go - -go: - - "1.12" - - "1.13" - -install: - - go get -v -t github.com/coreos/go-oidc/... - - go get golang.org/x/tools/cmd/cover - - go get golang.org/x/lint/golint - -script: - - ./test - -notifications: - email: false diff --git a/vendor/github.com/coreos/go-oidc/CONTRIBUTING.md b/vendor/github.com/coreos/go-oidc/CONTRIBUTING.md deleted file mode 100644 index 6662073a848..00000000000 --- a/vendor/github.com/coreos/go-oidc/CONTRIBUTING.md +++ /dev/null @@ -1,71 +0,0 @@ -# How to Contribute - -CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via -GitHub pull requests. This document outlines some of the conventions on -development workflow, commit message formatting, contact points and other -resources to make it easier to get your contribution accepted. - -# Certificate of Origin - -By contributing to this project you agree to the Developer Certificate of -Origin (DCO). This document was created by the Linux Kernel community and is a -simple statement that you, as a contributor, have the legal right to make the -contribution. See the [DCO](DCO) file for details. - -# Email and Chat - -The project currently uses the general CoreOS email list and IRC channel: -- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev) -- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org - -Please avoid emailing maintainers found in the MAINTAINERS file directly. They -are very busy and read the mailing lists. - -## Getting Started - -- Fork the repository on GitHub -- Read the [README](README.md) for build and test instructions -- Play with the project, submit bugs, submit patches! - -## Contribution Flow - -This is a rough outline of what a contributor's workflow looks like: - -- Create a topic branch from where you want to base your work (usually master). -- Make commits of logical units. -- Make sure your commit messages are in the proper format (see below). -- Push your changes to a topic branch in your fork of the repository. -- Make sure the tests pass, and add any new tests as appropriate. -- Submit a pull request to the original repository. - -Thanks for your contributions! - -### Format of the Commit Message - -We follow a rough convention for commit messages that is designed to answer two -questions: what changed and why. The subject line should feature the what and -the body of the commit should describe the why. - -``` -scripts: add the test-cluster command - -this uses tmux to setup a test cluster that you can easily kill and -start for debugging. - -Fixes #38 -``` - -The format can be described more formally as follows: - -``` -: - - - -