Compare commits

..

21 Commits

Author SHA1 Message Date
Matt Moyer
9b9e733a7d Merge pull request #662 from mattmoyer/parameterize-test-images
Parameterize our test images in ytt.
2021-06-03 15:53:13 -05:00
Matt Moyer
df78e00df3 Parameterize our test images in ytt.
These are images we use for local and some CI testing.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-06-03 15:25:09 -05:00
Matt Moyer
b5ed4e6a13 Merge pull request #660 from mattmoyer/fix-credentialissuer-service-type-field-typo
Fix typo in CredentialIssuer ytt template.
2021-06-03 14:01:14 -05:00
Matt Moyer
500b444bad Merge pull request #657 from vmware-tanzu/fix-ldap-supervisor-login-test-flake
Avoid a rare flake in TestSupervisorLogin.
2021-06-03 13:31:15 -05:00
Matt Moyer
d3e2859238 Merge pull request #658 from vmware-tanzu/fix-impersonation-notfound-handling
Tolerate NotFound when deleting services in `impersonatorconfig`.
2021-06-03 13:30:54 -05:00
Matt Moyer
5686591420 Avoid a rare flake in TestSupervisorLogin.
There was nothing to guarantee that _all_ Supervisor pods would be ready to handle this request. We saw a rare test flake where the LDAPIdentityProvider was marked as ready but one of the Supervisor pods didn't have it loaded yet and returned an HTTP 422 error (`Unprocessable Entity: No upstream providers are configured`).

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-06-03 12:13:56 -05:00
Matt Moyer
6903196c18 Fix a data race in TestImpersonationProxy.
The `require.Eventually()` function runs the body of the check in a separate goroutine, so it's not safe to use other `require` assertions as we did here. Our `library.RequireEventuallyWithoutError()` function does not spawn a goroutine, so it's safer to use here.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-06-03 12:07:19 -05:00
Matt Moyer
af4cd1b515 Tolerate NotFound when deleting services in impersonatorconfig.
When a CredentialIssuer is switched from one service type to another (or switched to disabled mode), the `impersonatorconfig` controller will delete the previous Service, if any. Normally one Concierge pod will succeed to delete this initially and any other pods will see a NotFound error.

Before this change, the NotFound would bubble up and cause the strategy to enter a ErrorDuringSetup status until the next reconcile loop. We now handle this case without reporting an error.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-06-03 12:07:19 -05:00
Matt Moyer
2acfafd5a5 Merge pull request #656 from vmware-tanzu/fix-credentialissuer-test-flake
Remove an invalid test assertion in TestCredentialIssuer.
2021-06-03 12:03:22 -05:00
anjalitelang
a5067cdbb3 Update ROADMAP.md
Updating Roadmap for June to reflect Device Code Flow and AD support
2021-06-03 12:33:36 -04:00
Matt Moyer
5aa08756e0 Fix typo in CredentialIssuer ytt template.
This typo wasn't caught in testing because 1) the Kubernetes API ignores the unknown field and 2) the `type` field defaults to `LoadBalancer` anyway, so things behave as expected.

Even though this doesn't cause any large problems, it's quite confusing.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-06-02 14:48:18 -05:00
Matt Moyer
0e66b0b165 Remove an invalid test assertion in TestCredentialIssuer.
The LastUpdateTime is no longer updated on every resync. It only changes if the underlying status has changed, so that it effectively shows when the transition happened.

This change happened in ab750f48aa, but we missed this test. It only fails when it has been more than ten minutes since the CredentialIssuer transitioned into a healthy state, but that can happen in our long-running CI environments.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-06-02 12:05:02 -05:00
Matt Moyer
87660611d2 Tweak blog post to add a shoutout.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-06-02 11:28:54 -05:00
Matt Moyer
9968c0d234 Fix my fix 🤦🏻 . 2021-06-02 11:06:03 -05:00
Matt Moyer
193fcb87bb Fix a typo on the "Community Meetings" time.
We had "PT" twice, when one of them should have been "ET".
2021-06-02 11:05:29 -05:00
Ryan Richard
a08e4ec043 Update architecture.md 2021-06-02 08:54:04 -07:00
Matt Moyer
e38a7548cc Link the v0.9.0 release from the blog post.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-06-02 10:24:17 -05:00
Pinny
b5dea42bbe Update CLI docs for v0.9.0 release 2021-06-02 15:22:13 +00:00
Matt Moyer
d06fe15a68 Merge pull request #655 from mattmoyer/update-docs-for-v0.9.0
Update docs for v0.9.0
2021-06-02 10:07:02 -05:00
Matt Moyer
e6301f0e74 Update latest version number in docs.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-06-02 10:05:07 -05:00
Matt Moyer
aca33e45fb Fix blog post date to match actual v0.9.0 release.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-06-02 10:02:59 -05:00
20 changed files with 93 additions and 70 deletions

View File

@@ -12,7 +12,7 @@ See [SCOPE.md](./SCOPE.md) for some guidelines about what we consider in and out
## Community Meetings
Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occuring every first and third Thursday of the month at 9AM PT / 12PM PT. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occuring every first and third Thursday of the month at 9AM PT / 12PM ET. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.

View File

@@ -38,7 +38,8 @@ Theme|Description|Timeline|
|--|--|--|
|LDAP Support|Extends upstream IDP protocols|May 2021|
|Improved Documentation|Reorganizing and improving Pinniped docs; new how-to guides and tutorials|May 2021|
|Multiple IDPs|Support for multiple upstream IDPs to be configured simultaneously|Jun 2021|
|Device Code Flow|Add support for OAuth 2.0 Device Authorization Grant in the Pinniped CLI and Supervisor|Jun 2021|
|AD Support|Extends upstream IDP protocols|Jun 2021|
|Wider Concierge cluster support|Support for more cluster types in the Concierge|Jul 2021|
|Improving Security Posture|Offer the best security posture for Kubernetes cluster authentication|Exploring/Ongoing|
|Improve our CI/CD systems|Upgrade tests; make Kind more efficient and reliable for CI ; Windows tests; performance tests; scale tests; soak tests|Exploring/Ongoing|

View File

@@ -260,7 +260,7 @@ spec:
externalEndpoint: #@ data.values.impersonation_proxy_spec.external_endpoint
#@ end
service:
mode: #@ data.values.impersonation_proxy_spec.service.mode
type: #@ data.values.impersonation_proxy_spec.service.type
#@ if data.values.impersonation_proxy_spec.service.load_balancer_ip:
loadBalancerIP: #@ data.values.impersonation_proxy_spec.service.load_balancer_ip
#@ end

View File

@@ -87,7 +87,7 @@ impersonation_proxy_spec:
#! impersonation proxy.
#! None does not provision either and assumes that you have set the external_endpoint
#! and set up your own ingress to connect to the impersonation proxy.
mode: LoadBalancer
type: LoadBalancer
#! The annotations that should be set on the ClusterIP or LoadBalancer Service.
annotations:
{service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "4000"}

View File

@@ -477,7 +477,8 @@ func (c *impersonatorConfigController) ensureLoadBalancerIsStopped(ctx context.C
c.infoLog.Info("deleting load balancer for impersonation proxy",
"service", klog.KRef(c.namespace, c.generatedLoadBalancerServiceName),
)
return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedLoadBalancerServiceName, metav1.DeleteOptions{})
err = c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedLoadBalancerServiceName, metav1.DeleteOptions{})
return utilerrors.FilterOut(err, k8serrors.IsNotFound)
}
func (c *impersonatorConfigController) ensureClusterIPServiceIsStarted(ctx context.Context, config *v1alpha1.ImpersonationProxySpec) error {
@@ -516,7 +517,8 @@ func (c *impersonatorConfigController) ensureClusterIPServiceIsStopped(ctx conte
c.infoLog.Info("deleting cluster ip for impersonation proxy",
"service", klog.KRef(c.namespace, c.generatedClusterIPServiceName),
)
return c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedClusterIPServiceName, metav1.DeleteOptions{})
err = c.k8sClient.CoreV1().Services(c.namespace).Delete(ctx, c.generatedClusterIPServiceName, metav1.DeleteOptions{})
return utilerrors.FilterOut(err, k8serrors.IsNotFound)
}
func (c *impersonatorConfigController) createOrUpdateService(ctx context.Context, service *v1.Service) error {

View File

@@ -43,6 +43,8 @@ Pinniped supports the following IDPs.
identity provider (e.g., [Dex](https://github.com/dexidp/dex),
[Okta](https://www.okta.com/)).
1. Any [LDAP](https://ldap.com) identity provider.
The
[`idp.supervisor.pinniped.dev`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-idp-supervisor-pinniped-dev-v1alpha1)
API group contains the Kubernetes custom resources that configure the Pinniped

View File

@@ -44,10 +44,10 @@ Click Open to allow the command to proceed.
## Install a specific version via script
For example, to install v0.8.0 on Linux/amd64:
For example, to install v0.9.0 on Linux/amd64:
```sh
curl -Lso pinniped https://get.pinniped.dev/v0.8.0/pinniped-cli-linux-amd64 \
curl -Lso pinniped https://get.pinniped.dev/v0.9.0/pinniped-cli-linux-amd64 \
&& chmod +x pinniped \
&& sudo mv pinniped /usr/local/bin/pinniped
```

View File

@@ -26,9 +26,9 @@ Warning: the default configuration may create a public LoadBalancer Service on y
1. Install the Concierge into the `pinniped-concierge` namespace with default options:
- `kubectl apply -f https://get.pinniped.dev/v0.8.0/install-pinniped-concierge.yaml`
- `kubectl apply -f https://get.pinniped.dev/v0.9.0/install-pinniped-concierge.yaml`
*Replace v0.8.0 with your preferred version number.*
*Replace v0.9.0 with your preferred version number.*
## With custom options

View File

@@ -25,9 +25,9 @@ You should have a supported Kubernetes cluster with working HTTPS ingress capabi
1. Install the Supervisor into the `pinniped-supervisor` namespace with default options:
- `kubectl apply -f https://get.pinniped.dev/v0.8.0/install-pinniped-supervisor.yaml`
- `kubectl apply -f https://get.pinniped.dev/v0.9.0/install-pinniped-supervisor.yaml`
*Replace v0.8.0 with your preferred version number.*
*Replace v0.9.0 with your preferred version number.*
## With custom options

View File

@@ -21,33 +21,35 @@ pinniped get kubeconfig [flags]
### Options
```
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
--concierge-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the Concierge
--concierge-credential-issuer string Concierge CredentialIssuer object to use for autodiscovery (default: autodiscover)
--concierge-endpoint string API base for the Concierge endpoint
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
--concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false)
--credential-cache string Path to cluster-specific credentials cache
--generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped")
-h, --help help for kubeconfig
--kubeconfig string Path to kubeconfig file
--kubeconfig-context string Kubeconfig context name (default: current active context)
--no-concierge Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly
--oidc-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
--oidc-client-id string OpenID Connect client ID (default: autodiscover) (default "pinniped-cli")
--oidc-issuer string OpenID Connect issuer URL (default: autodiscover)
--oidc-listen-port uint16 TCP port for localhost listener (authorization code flow only)
--oidc-request-audience string Request a token with an alternate audience using RFC8693 token exchange
--oidc-scopes strings OpenID Connect scopes to request during login (default [offline_access,openid,pinniped:request-audience])
--oidc-session-cache string Path to OpenID Connect session cache file
--oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
-o, --output string Output file path (default: stdout)
--skip-validation Skip final validation of the kubeconfig (default: false)
--static-token string Instead of doing an OIDC-based login, specify a static token
--static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
--timeout duration Timeout for autodiscovery and validation (default 10m0s)
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
--concierge-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the Concierge
--concierge-credential-issuer string Concierge CredentialIssuer object to use for autodiscovery (default: autodiscover)
--concierge-endpoint string API base for the Concierge endpoint
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
--concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false)
--credential-cache string Path to cluster-specific credentials cache
--generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped")
-h, --help help for kubeconfig
--kubeconfig string Path to kubeconfig file
--kubeconfig-context string Kubeconfig context name (default: current active context)
--no-concierge Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly
--oidc-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
--oidc-client-id string OpenID Connect client ID (default: autodiscover) (default "pinniped-cli")
--oidc-issuer string OpenID Connect issuer URL (default: autodiscover)
--oidc-listen-port uint16 TCP port for localhost listener (authorization code flow only)
--oidc-request-audience string Request a token with an alternate audience using RFC8693 token exchange
--oidc-scopes strings OpenID Connect scopes to request during login (default [offline_access,openid,pinniped:request-audience])
--oidc-session-cache string Path to OpenID Connect session cache file
--oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
-o, --output string Output file path (default: stdout)
--skip-validation Skip final validation of the kubeconfig (default: false)
--static-token string Instead of doing an OIDC-based login, specify a static token
--static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
--timeout duration Timeout for autodiscovery and validation (default 10m0s)
--upstream-identity-provider-name string The name of the upstream identity provider used during login with a Supervisor
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap')
```
### SEE ALSO

View File

@@ -79,7 +79,7 @@ as the authenticator.
see [deploy/local-user-authenticator/README.md](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/local-user-authenticator/README.md)
for instructions on how to deploy using `ytt`.
If you prefer to install a specific version, replace `latest` in the URL with the version number such as `v0.8.0`.
If you prefer to install a specific version, replace `latest` in the URL with the version number such as `v0.9.0`.
1. Create a test user named `pinny-the-seal` in the local-user-authenticator namespace.

View File

@@ -1,7 +1,7 @@
---
title: "Pinniped v0.9.0: Bring Your LDAP Identities to Your Kubernetes Clusters"
slug: bringing-ldap-identities-to-clusters
date: 2021-05-31
date: 2021-06-02
author: Ryan Richard
image: https://cdn.pixabay.com/photo/2018/08/05/15/06/seal-3585727_1280.jpg
excerpt: "With the release of v0.9.0, Pinniped now supports using LDAP identities to log in to Kubernetes clusters."
@@ -12,7 +12,7 @@ tags: ['Ryan Richard', 'release']
*Photo from [matos11 on Pixabay](https://pixabay.com/photos/seal-animal-water-hairy-3585727/)*
Pinniped is a “batteries included” authentication system for Kubernetes clusters.
With the release of v0.9.0, Pinniped now supports using LDAP identities to log in to Kubernetes clusters.
With the [release of v0.9.0](https://github.com/vmware-tanzu/pinniped/releases/tag/v0.9.0), Pinniped now supports using LDAP identities to log in to Kubernetes clusters.
This post describes how v0.9.0 fits into Pinnipeds quest to bring a smooth, unified login experience to all Kubernetes clusters.
@@ -136,7 +136,9 @@ Additionally, SAML seems to be waning in popularity in favor of OIDC, which prov
What do you think? Do you still use SAML in your enterprise?
Do you need SAML for authentication into your Kubernetes clusters? Let us know!
### We'd love to hear from you!
## Community contributors
The Pinniped community continues to grow, and is a vital part of the project's success. This release includes important feedback and contributions from community user [@jeuniii](https://github.com/jeuniii). Thank you for helping improve Pinniped!
We thrive on community feedback. Did you try our new LDAP features?
What else do you need from identity systems for your Kubernetes clusters?

View File

@@ -1,5 +1,8 @@
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
---
apiVersion: v1
kind: ServiceAccount
@@ -50,8 +53,7 @@ spec:
serviceAccountName: cert-issuer
initContainers:
- name: generate-certs
image: cfssl/cfssl:1.5.0
imagePullPolicy: IfNotPresent
image: #@ data.values.cfssl_image
command: ["/bin/bash"]
args:
- -c
@@ -108,7 +110,7 @@ spec:
mountPath: /var/certs
containers:
- name: save-certs
image: bitnami/kubectl
image: #@ data.values.kubectl_image
command: ["/bin/bash"]
args:
- -c

View File

@@ -68,8 +68,7 @@ spec:
spec:
containers:
- name: dex
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: IfNotPresent
image: #@ data.values.dex_image
command:
- /usr/local/bin/dex
- serve

View File

@@ -209,8 +209,7 @@ spec:
#! Use our own fork of docker.io/bitnami/openldap for now, because we added the
#! LDAP_SERVER_CONFIG_BEFORE_CUSTOM_LDIF_DIR and LDAP_SERVER_CONFIG_AFTER_CUSTOM_LDIF_DIR options.
#! See https://github.com/pinniped-ci-bot/bitnami-docker-openldap/tree/pinniped
image: projects.registry.vmware.com/pinniped/test-ldap:latest
imagePullPolicy: Always
image: #@ data.values.ldap_image
ports:
- name: ldap
containerPort: 1389

View File

@@ -25,8 +25,7 @@ spec:
emptyDir: {}
containers:
- name: proxy
image: projects.registry.vmware.com/pinniped/test-forward-proxy
imagePullPolicy: Always
image: #@ data.values.proxy_image
ports:
- name: http
containerPort: 3128
@@ -48,7 +47,7 @@ spec:
periodSeconds: 5
failureThreshold: 2
- name: accesslogs
image: debian:10.8-slim
image: #@ data.values.proxy_image
command:
- "/bin/sh"
- "-c"

View File

@@ -25,3 +25,10 @@ pinny_bcrypt_passwd_hash:
#! The plaintext password of the LDAP test account user.
pinny_ldap_password:
#! Images for each of the deployed test components.
dex_image: ghcr.io/dexidp/dex:v2.27.0
ldap_image: projects.registry.vmware.com/pinniped/test-ldap:latest
proxy_image: projects.registry.vmware.com/pinniped/test-forward-proxy:latest
cfssl_image: cfssl/cfssl:1.5.0
kubectl_image: bitnami/kubectl:latest

View File

@@ -87,16 +87,6 @@ func TestCredentialIssuer(t *testing.T) {
},
actualStatusKubeConfigInfo,
)
// Only validate LastUpdateTime when cluster signing key is available. The last update time
// will be set every time our controllers resync, but only when there exists controller
// manager pods (all other pods will be filtered out), hence why this assertion is in this
// if branch.
//
// This behavior is up for debate. We should eventually discuss the contract for this
// LastUpdateTime field and ensure that the implementation is the same for when the cluster
// signing key is available and not available.
require.WithinDuration(t, time.Now(), actualStatusStrategy.LastUpdateTime.Local(), 10*time.Minute)
} else {
require.Equal(t, configv1alpha1.ErrorStrategyStatus, actualStatusStrategy.Status)
require.Equal(t, configv1alpha1.CouldNotFetchKeyStrategyReason, actualStatusStrategy.Reason)

View File

@@ -1267,9 +1267,9 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
})
// wait until the credential issuer is updated with the new url
require.Eventually(t, func() bool {
library.RequireEventuallyWithoutError(t, func() (bool, error) {
newImpersonationProxyURL, _ := performImpersonatorDiscovery(ctx, t, env, adminConciergeClient)
return newImpersonationProxyURL == "https://"+clusterIPServiceURL
return newImpersonationProxyURL == "https://"+clusterIPServiceURL, nil
}, 30*time.Second, 500*time.Millisecond)
newImpersonationProxyURL, newImpersonationProxyCACertPEM := performImpersonatorDiscovery(ctx, t, env, adminConciergeClient)

View File

@@ -540,11 +540,29 @@ func requestAuthorizationUsingLDAPIdentityProvider(t *testing.T, downstreamAutho
authRequest.Header.Set("Pinniped-Username", upstreamUsername)
authRequest.Header.Set("Pinniped-Password", upstreamPassword)
authResponse, err := httpClient.Do(authRequest)
require.NoError(t, err)
responseBody, err := ioutil.ReadAll(authResponse.Body)
defer authResponse.Body.Close()
require.NoError(t, err)
// At this point in the test, we've already waited for the LDAPIdentityProvider to be loaded and marked healthy by
// at least one Supervisor pod, but we can't be sure that _all_ of them have loaded the provider, so we may need
// to retry this request multiple times until we get the expected 302 status response.
var authResponse *http.Response
var responseBody []byte
library.RequireEventuallyWithoutError(t, func() (bool, error) {
authResponse, err = httpClient.Do(authRequest)
if err != nil {
t.Logf("got authorization response with error %v", err)
return false, nil
}
defer func() { _ = authResponse.Body.Close() }()
responseBody, err = ioutil.ReadAll(authResponse.Body)
if err != nil {
return false, nil
}
t.Logf("got authorization response with code %d (%d byte body)", authResponse.StatusCode, len(responseBody))
if authResponse.StatusCode != http.StatusFound {
return false, nil
}
return true, nil
}, 30*time.Second, 200*time.Millisecond)
expectSecurityHeaders(t, authResponse, true)
// A successful authorize request results in a redirect to our localhost callback listener with an authcode param.