Merge branch 'main' into dynamic_clients

This commit is contained in:
Ryan Richard 2022-06-10 12:52:59 -07:00
commit e7096c61a8
8 changed files with 146 additions and 60 deletions

View File

@ -7,8 +7,8 @@ params:
github_url: "https://github.com/vmware-tanzu/pinniped"
slack_url: "https://kubernetes.slack.com/messages/pinniped"
community_url: "https://go.pinniped.dev/community"
latest_version: v0.17.0
latest_codegen_version: 1.23
latest_version: v0.18.0
latest_codegen_version: 1.24
pygmentsCodefences: true
pygmentsStyle: "pygments"
markup:

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View File

@ -0,0 +1,91 @@
---
title: "Pinniped v0.18.0: With User-Friendly features such as JSON formatted logs, LDAP/ActiveDirectory UI Support"
slug: formatted-logs-ui-based-ldap-logins
date: 2022-06-08
author: Anjali Telang
image: https://images.unsplash.com/photo-1587738972117-c12f8389f1d4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1752&q=80
excerpt: "With v0.18.0 you get cool new features like nicely formatted JSON logs, UI to login to your LDAP or ActiveDirectory Identity Provider, and more"
tags: ['Margo Crawford','Ryan Richard', 'Mo Khan', 'Anjali Telang', 'release']
---
![Friendly seal](https://images.unsplash.com/photo-1587738972117-c12f8389f1d4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1752&q=80)
*Photo by [Steve Adams](https://unsplash.com/@sradams57) on [Unsplash](https://unsplash.com/s/photos/seal)*
We've listened to your requests and are excited to bring some cool user-friendly features that will enhance your Kubernetes Authentication experience. From this release onwards, we will have Pinniped logs in JSON format. We also bring you the ability to use a User Interface (UI) to login with your LDAP or ActiveDirectory credentials.
## JSON Formatted logs
[Kubernetes 1.19](https://kubernetes.io/blog/2020/09/04/kubernetes-1-19-introducing-structured-logs/) introduced the ability to have logs emitted in JSON log format. There are many benefits to using JSON as listed in this [KEP for structured logging](https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/1602-structured-logging#json-output-format) :
* Broadly adopted by logging libraries with very efficient implementations (zap, zerolog).
* Out of the box support by many logging backends (Elasticsearch, Stackdriver, BigQuery, Splunk)
* Easily parsable and transformable
* Existing tools for ad-hoc analysis (jq)
Considering these benefits, from this release onwards we will *default to JSON formatted logs* for the Pinniped Supervisor and Concierge components.
**Users please note: We are deprecating the text based log format with this release and we will remove the configuration in a future release.**
We realize that it may take time for some users to adapt to this new format especially if they have existing tooling around *text* based logs. With that in mind, we will allow users to deploy Pinniped Supervisor or Concierge by setting *deprecated_log_format:text* in the values.yaml deployment file for these components. However, Please consider moving to json formatted logs for compatibility with latest releases. We will announce 2 releases prior to removing the field, as is our policy for removing configurations.
With this release, the Pinniped CLI logs have also been enhanced to emit useful information such as timestamps and line numbers of files, as shown in the example below:
```
$ pinniped get kubeconfig
Tue, 24 May 2022 11:18:50 EDT cmd/kubeconfig.go:584 discovered CredentialIssuer {"name": "concierge-config"}
Tue, 24 May 2022 11:18:50 EDT cmd/kubeconfig.go:419 discovered Concierge operating in impersonation proxy mode
Tue, 24 May 2022 11:18:50 EDT cmd/kubeconfig.go:432 discovered Concierge endpoint {"endpoint": "https://abcd"}
Tue, 24 May 2022 11:18:50 EDT cmd/kubeconfig.go:447 discovered Concierge certificate authority bundle {"roots": 1}
Tue, 24 May 2022 11:18:50 EDT cmd/kubeconfig.go:469 discovered WebhookAuthenticator {"name": "wa"}
```
## LDAP User Interface
With [v0.18.0](https://github.com/vmware-tanzu/pinniped/releases/tag/v0.18.0) we are also adding support for a User Interface that users can access to input their credentials and login to their LDAP or Active Directory Identity Provider(IDP). This feature is a first step in our effort to provide a UI-driven workflow for our users. We will have more features that support UI workflows coming up in our next release, so stay tuned for that!
When using the Pinniped CLI, a successful login takes the user to the regular form_post success page, just like the previously supported *browser authcode flow* for an OIDC IDP.
This feature changes how the `pinniped get kubeconfig` cli deals with ambiguous flows. Previously, if there was more than one flow advertised for an IDP, the cli would require users to use the `--upstream-identity-provider-flow` flag. Now, it chooses the first flow type in the Supervisor's discovery response by default.
Here's a snapshot of the UI hosted by the Pinniped Supervisor:
![LDAP / ActiveDirectory UI for login ](/docs/img/ldap-ad-ui.png)
### Features included in the UI
The UI will provide the following features for now:
* A username and password fields along with the submit button
* Generalized error messaging for failed logins that do not expose sensitive information
* Provides information easily allowing a user to identify the screen as belonging to Pinniped with the name of their Identity Provider displayed on the page
* Addresses basic security concerns for web forms such as use of HTTPS, a password field, CSRF protection and redirect protection
* Prevents LDAP injection attacks
* Screens that are accessible and friendly to screen readers
* Screens that are friendly to password managers
### Future enhancements for the UI
This is our effort to provide a basic UI for logins. We will enhance the UI further in future releases based on the feedback we receive from our users.
This can include (but is not limited to) the following features:
* Advanced UI features such as remember me and reveal password
* Branding & customization, besides what we provide today with the basic Pinniped information.
* Support for SSO integrations
* Internationalization or localization. Note that the CLI doesn't currently support this either.
*Note* that Pinniped relies on the user's IDP to address advanced security concerns such as brute force protection, username enumeration, etc.
## What else is in this release?
Refer to the [release notes for v0.18.0](https://github.com/vmware-tanzu/pinniped/releases/tag/v0.18.0) for a complete list of fixes and features included in the release.
## Community contributors
The Pinniped community continues to grow, and is a vital part of the project's success.
[Are you using Pinniped?](https://github.com/vmware-tanzu/pinniped/discussions/152)
Have you tried any of our new features? Let us know what you think of our logging and UI features and if you are looking for any of the enhancements mentioned above.
We thrive on community feedback and would like to hear more!
Reach out to us in [#pinniped](https://kubernetes.slack.com/archives/C01BW364RJA) on Kubernetes Slack,
[create an issue](https://github.com/vmware-tanzu/pinniped/issues/new/choose) on our Github repository,
or start a [discussion](https://github.com/vmware-tanzu/pinniped/discussions).
{{< community >}}

View File

@ -1,4 +1,4 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package integration
@ -32,19 +32,20 @@ func runTestKubectlCommand(t *testing.T, args ...string) (string, string) {
func requireCleanKubectlStderr(t *testing.T, stderr string) {
// Every line must be empty or contain a known, innocuous warning.
for _, line := range strings.Split(stderr, "\n") {
if strings.TrimSpace(line) == "" {
continue
}
if strings.Contains(line, "Throttling request took") {
continue
}
if strings.Contains(line, "due to client-side throttling, not priority and fairness") {
continue
}
switch {
case strings.TrimSpace(line) == "",
strings.Contains(line, "Throttling request took"),
strings.Contains(line, "due to client-side throttling, not priority and fairness"),
strings.Contains(line, "the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead"),
strings.Contains(line, "To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke"):
// ignore these allowed stderr lines
default:
// anything else is a failure
require.Failf(t, "unexpected kubectl stderr", "kubectl produced unexpected stderr:\n%s\n\n", stderr)
return
}
}
}
func TestGetPinnipedCategory(t *testing.T) {
env := testlib.IntegrationEnv(t)

View File

@ -634,15 +634,13 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
// Add an LDAP upstream IDP and try using it to authenticate during kubectl commands
// by interacting with the CLI's username and password prompts.
t.Run("with Supervisor LDAP upstream IDP using username and password prompts", func(t *testing.T) {
testlib.SkipTestWhenLDAPIsUnavailable(t, env)
testCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
t.Cleanup(cancel)
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
t.Skip("LDAP integration test requires connectivity to an LDAP server")
}
expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue
expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs
@ -696,15 +694,13 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
// Add an LDAP upstream IDP and try using it to authenticate during kubectl commands
// by passing username and password via environment variables, thus avoiding the CLI's username and password prompts.
t.Run("with Supervisor LDAP upstream IDP using PINNIPED_USERNAME and PINNIPED_PASSWORD env vars", func(t *testing.T) {
testlib.SkipTestWhenLDAPIsUnavailable(t, env)
testCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
t.Cleanup(cancel)
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
t.Skip("LDAP integration test requires connectivity to an LDAP server")
}
expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue
expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs
@ -770,18 +766,13 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
// Add an Active Directory upstream IDP and try using it to authenticate during kubectl commands
// by interacting with the CLI's username and password prompts.
t.Run("with Supervisor ActiveDirectory upstream IDP using username and password prompts", func(t *testing.T) {
testlib.SkipTestWhenActiveDirectoryIsUnavailable(t, env)
testCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
t.Cleanup(cancel)
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
t.Skip("Active Directory integration test requires connectivity to an LDAP server")
}
if env.SupervisorUpstreamActiveDirectory.Host == "" {
t.Skip("Active Directory hostname not specified")
}
expectedUsername := env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue
expectedGroups := env.SupervisorUpstreamActiveDirectory.TestUserIndirectGroupsSAMAccountPlusDomainNames
@ -835,19 +826,13 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
// Add an ActiveDirectory upstream IDP and try using it to authenticate during kubectl commands
// by passing username and password via environment variables, thus avoiding the CLI's username and password prompts.
t.Run("with Supervisor ActiveDirectory upstream IDP using PINNIPED_USERNAME and PINNIPED_PASSWORD env vars", func(t *testing.T) {
testlib.SkipTestWhenActiveDirectoryIsUnavailable(t, env)
testCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
t.Cleanup(cancel)
tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
t.Skip("ActiveDirectory integration test requires connectivity to an LDAP server")
}
if env.SupervisorUpstreamActiveDirectory.Host == "" {
t.Skip("Active Directory hostname not specified")
}
expectedUsername := env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue
expectedGroups := env.SupervisorUpstreamActiveDirectory.TestUserIndirectGroupsSAMAccountPlusDomainNames
@ -912,6 +897,8 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
// Add an LDAP upstream IDP and try using it to authenticate during kubectl commands, using the browser flow.
t.Run("with Supervisor LDAP upstream IDP and browser flow with with form_post automatic authcode delivery to CLI", func(t *testing.T) {
testlib.SkipTestWhenLDAPIsUnavailable(t, env)
testCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
t.Cleanup(cancel)
@ -966,6 +953,8 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
// Add an Active Directory upstream IDP and try using it to authenticate during kubectl commands, using the browser flow.
t.Run("with Supervisor Active Directory upstream IDP and browser flow with with form_post automatic authcode delivery to CLI", func(t *testing.T) {
testlib.SkipTestWhenActiveDirectoryIsUnavailable(t, env)
testCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
t.Cleanup(cancel)
@ -974,13 +963,6 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
// Start a fresh browser driver because we don't want to share cookies between the various tests in this file.
page := browsertest.Open(t)
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
t.Skip("Active Directory integration test requires connectivity to an LDAP server")
}
if env.SupervisorUpstreamActiveDirectory.Host == "" {
t.Skip("Active Directory hostname not specified")
}
expectedUsername := env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue
expectedGroups := env.SupervisorUpstreamActiveDirectory.TestUserIndirectGroupsSAMAccountPlusDomainNames
@ -1027,6 +1009,8 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
// Add an LDAP upstream IDP and try using it to authenticate during kubectl commands, using the env var to choose the browser flow.
t.Run("with Supervisor LDAP upstream IDP and browser flow selected by env var override with with form_post automatic authcode delivery to CLI", func(t *testing.T) {
testlib.SkipTestWhenLDAPIsUnavailable(t, env)
testCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
t.Cleanup(cancel)

View File

@ -48,17 +48,12 @@ func TestSupervisorLogin_Browser(t *testing.T) {
skipLDAPTests := func(t *testing.T) {
t.Helper()
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
t.Skip("LDAP integration test requires connectivity to an LDAP server")
}
testlib.SkipTestWhenLDAPIsUnavailable(t, env)
}
skipActiveDirectoryTests := func(t *testing.T) {
t.Helper()
skipLDAPTests(t)
if env.SupervisorUpstreamActiveDirectory.Host == "" {
t.Skip("Active Directory hostname not specified")
}
testlib.SkipTestWhenActiveDirectoryIsUnavailable(t, env)
}
basicOIDCIdentityProviderSpec := func() idpv1alpha1.OIDCIdentityProviderSpec {

View File

@ -103,9 +103,7 @@ func TestSupervisorWarnings_Browser(t *testing.T) {
)
t.Run("LDAP group refresh flow", func(t *testing.T) {
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
t.Skip("LDAP integration test requires connectivity to an LDAP server")
}
testlib.SkipTestWhenLDAPIsUnavailable(t, env)
expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue
@ -242,13 +240,9 @@ func TestSupervisorWarnings_Browser(t *testing.T) {
t.Logf("second kubectl command took %s", time.Since(startTime2).String())
})
t.Run("Active Directory group refresh flow", func(t *testing.T) {
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
t.Skip("LDAP integration test requires connectivity to an LDAP server")
}
if env.SupervisorUpstreamActiveDirectory.Host == "" {
t.Skip("Active Directory hostname not specified")
}
testlib.SkipTestWhenActiveDirectoryIsUnavailable(t, env)
expectedUsername, password := testlib.CreateFreshADTestUser(t, env)
t.Cleanup(func() {

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package testlib
@ -8,7 +8,28 @@ import "testing"
// skipUnlessIntegration skips the current test if `-short` has been passed to `go test`.
func skipUnlessIntegration(t *testing.T) {
t.Helper()
if testing.Short() {
t.Skip("skipping integration test because of '-short' flag")
}
}
func SkipTestWhenLDAPIsUnavailable(t *testing.T, env *TestEnv) {
t.Helper()
if len(env.ToolsNamespace) == 0 && !env.HasCapability(CanReachInternetLDAPPorts) {
t.Skip("LDAP integration test requires connectivity to an LDAP server")
}
}
func SkipTestWhenActiveDirectoryIsUnavailable(t *testing.T, env *TestEnv) {
t.Helper()
if !env.HasCapability(CanReachInternetLDAPPorts) {
t.Skip("Active Directory integration test requires network connectivity to an AD server")
}
if IntegrationEnv(t).SupervisorUpstreamActiveDirectory.Host == "" {
t.Skip("Active Directory hostname not specified")
}
}