Merge pull request #88049 from mtaufen/provider-info-agnhost

Update agnhost to test OIDC validation of JWT tokens
This commit is contained in:
Kubernetes Prow Robot 2020-03-04 03:43:47 -08:00 committed by GitHub
commit f692f5cfcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 209 additions and 11 deletions

1
go.mod
View File

@ -38,6 +38,7 @@ require (
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20 // indirect
github.com/containernetworking/cni v0.7.1
github.com/coredns/corefile-migration v1.0.6
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea
github.com/cpuguy83/go-md2man v1.0.10

View File

@ -32,6 +32,7 @@ go_library(
"//test/images/agnhost/nettest:go_default_library",
"//test/images/agnhost/no-snat-test:go_default_library",
"//test/images/agnhost/no-snat-test-proxy:go_default_library",
"//test/images/agnhost/openidmetadata:go_default_library",
"//test/images/agnhost/pause:go_default_library",
"//test/images/agnhost/port-forward-tester:go_default_library",
"//test/images/agnhost/porter:go_default_library",
@ -71,6 +72,7 @@ filegroup(
"//test/images/agnhost/nettest:all-srcs",
"//test/images/agnhost/no-snat-test:all-srcs",
"//test/images/agnhost/no-snat-test-proxy:all-srcs",
"//test/images/agnhost/openidmetadata:all-srcs",
"//test/images/agnhost/pause:all-srcs",
"//test/images/agnhost/port-forward-tester:all-srcs",
"//test/images/agnhost/porter:all-srcs",

View File

@ -1 +1 @@
2.11
2.12

View File

@ -22,33 +22,34 @@ import (
"github.com/spf13/cobra"
"k8s.io/klog"
"k8s.io/kubernetes/test/images/agnhost/audit-proxy"
auditproxy "k8s.io/kubernetes/test/images/agnhost/audit-proxy"
"k8s.io/kubernetes/test/images/agnhost/connect"
"k8s.io/kubernetes/test/images/agnhost/crd-conversion-webhook"
crdconvwebhook "k8s.io/kubernetes/test/images/agnhost/crd-conversion-webhook"
"k8s.io/kubernetes/test/images/agnhost/dns"
"k8s.io/kubernetes/test/images/agnhost/entrypoint-tester"
"k8s.io/kubernetes/test/images/agnhost/fakegitserver"
"k8s.io/kubernetes/test/images/agnhost/guestbook"
"k8s.io/kubernetes/test/images/agnhost/inclusterclient"
"k8s.io/kubernetes/test/images/agnhost/liveness"
"k8s.io/kubernetes/test/images/agnhost/logs-generator"
logsgen "k8s.io/kubernetes/test/images/agnhost/logs-generator"
"k8s.io/kubernetes/test/images/agnhost/mounttest"
"k8s.io/kubernetes/test/images/agnhost/net"
"k8s.io/kubernetes/test/images/agnhost/netexec"
"k8s.io/kubernetes/test/images/agnhost/nettest"
"k8s.io/kubernetes/test/images/agnhost/no-snat-test"
"k8s.io/kubernetes/test/images/agnhost/no-snat-test-proxy"
nosnat "k8s.io/kubernetes/test/images/agnhost/no-snat-test"
nosnatproxy "k8s.io/kubernetes/test/images/agnhost/no-snat-test-proxy"
"k8s.io/kubernetes/test/images/agnhost/openidmetadata"
"k8s.io/kubernetes/test/images/agnhost/pause"
"k8s.io/kubernetes/test/images/agnhost/port-forward-tester"
portforwardtester "k8s.io/kubernetes/test/images/agnhost/port-forward-tester"
"k8s.io/kubernetes/test/images/agnhost/porter"
"k8s.io/kubernetes/test/images/agnhost/resource-consumer-controller"
"k8s.io/kubernetes/test/images/agnhost/serve-hostname"
"k8s.io/kubernetes/test/images/agnhost/test-webserver"
resconsumerctrl "k8s.io/kubernetes/test/images/agnhost/resource-consumer-controller"
servehostname "k8s.io/kubernetes/test/images/agnhost/serve-hostname"
testwebserver "k8s.io/kubernetes/test/images/agnhost/test-webserver"
"k8s.io/kubernetes/test/images/agnhost/webhook"
)
func main() {
rootCmd := &cobra.Command{Use: "app", Version: "2.11"}
rootCmd := &cobra.Command{Use: "app", Version: "2.12"}
rootCmd.AddCommand(auditproxy.CmdAuditProxy)
rootCmd.AddCommand(connect.CmdConnect)
@ -75,6 +76,7 @@ func main() {
rootCmd.AddCommand(servehostname.CmdServeHostname)
rootCmd.AddCommand(testwebserver.CmdTestWebserver)
rootCmd.AddCommand(webhook.CmdWebhook)
rootCmd.AddCommand(openidmetadata.CmdTestServiceAccountIssuerDiscovery)
// NOTE(claudiub): Some tests are passing logging related flags, so we need to be able to
// accept them. This will also include them in the printed help.

View File

@ -0,0 +1,29 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["openidmetadata.go"],
importpath = "k8s.io/kubernetes/test/images/agnhost/openidmetadata",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//vendor/github.com/coreos/go-oidc:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/golang.org/x/oauth2:go_default_library",
"//vendor/gopkg.in/square/go-jose.v2/jwt:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,164 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package openidmetadata tests the OIDC discovery endpoints which are part of
// the ServiceAccountIssuerDiscovery feature.
package openidmetadata
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
oidc "github.com/coreos/go-oidc"
"github.com/spf13/cobra"
"golang.org/x/oauth2"
"gopkg.in/square/go-jose.v2/jwt"
"k8s.io/client-go/rest"
)
// CmdTestServiceAccountIssuerDiscovery is used by agnhost Cobra.
var CmdTestServiceAccountIssuerDiscovery = &cobra.Command{
Use: "test-service-account-issuer-discovery",
Short: "Tests the ServiceAccountIssuerDiscovery feature",
Long: "Reads in a mounted token and attempts to verify it against the API server's " +
"OIDC endpoints, using a third-party OIDC implementation.",
Args: cobra.MaximumNArgs(0),
Run: main,
}
var (
tokenPath string
audience string
inClusterDiscovery bool
)
func init() {
fs := CmdTestServiceAccountIssuerDiscovery.Flags()
fs.StringVar(&tokenPath, "token-path", "", "Path to read service account token from.")
fs.StringVar(&audience, "audience", "", "Audience to check on received token.")
fs.BoolVar(&inClusterDiscovery, "in-cluster-discovery", false,
"Includes the in-cluster bearer token in request headers. "+
"Use when validating against API server's discovery endpoints, "+
"which require authentication.")
}
func main(cmd *cobra.Command, args []string) {
ctx, err := withOAuth2Client(context.Background())
if err != nil {
log.Fatal(err)
}
raw, err := gettoken()
if err != nil {
log.Fatal(err)
}
log.Print("OK: Got token")
tok, err := jwt.ParseSigned(raw)
if err != nil {
log.Fatal(err)
}
var unsafeClaims claims
if err := tok.UnsafeClaimsWithoutVerification(&unsafeClaims); err != nil {
log.Fatal(err)
}
log.Printf("OK: got issuer %s", unsafeClaims.Issuer)
log.Printf("Full, not-validated claims: \n%#v", unsafeClaims)
iss, err := oidc.NewProvider(ctx, unsafeClaims.Issuer)
if err != nil {
log.Fatal(err)
}
log.Printf("OK: Constructed OIDC provider for issuer %v", unsafeClaims.Issuer)
validTok, err := iss.Verifier(&oidc.Config{ClientID: audience}).Verify(ctx, raw)
if err != nil {
log.Fatal(err)
}
log.Print("OK: Validated signature on JWT")
var safeClaims claims
if err := validTok.Claims(&safeClaims); err != nil {
log.Fatal(err)
}
log.Print("OK: Got valid claims from token!")
log.Printf("Full, validated claims: \n%#v", &safeClaims)
}
type kubeName struct {
Name string `json:"name"`
UID string `json:"uid"`
}
type kubeClaims struct {
Namespace string `json:"namespace"`
ServiceAccount kubeName `json:"serviceaccount"`
}
type claims struct {
jwt.Claims
Kubernetes kubeClaims `json:"kubernetes.io"`
}
func (k *claims) String() string {
return fmt.Sprintf("%s/%s for %s", k.Kubernetes.Namespace, k.Kubernetes.ServiceAccount.Name, k.Audience)
}
func gettoken() (string, error) {
b, err := ioutil.ReadFile(tokenPath)
return string(b), err
}
// withOAuth2Client returns a context that includes an HTTP Client, under the
// oauth2.HTTPClient key. If --in-cluster-discovery is true, the client will
// use the Kubernetes InClusterConfig. Otherwise it will use
// http.DefaultTransport.
// The `oidc` library respects the oauth2.HTTPClient context key; if it is set,
// the library will use the provided http.Client rather than the default
// HTTP client.
// This allows us to ensure requests get routed to the API server for
// --in-cluster-discovery, in a client configured with the appropriate CA.
func withOAuth2Client(context.Context) (context.Context, error) {
// TODO(mtaufen): Someday, might want to change this so that we can test
// TokenProjection with an API audience set to the external provider with
// requests against external endpoints (in which case we'd send
// a different token with a non-Kubernetes audience).
// By default, use the default http transport with the system root bundle,
// since it's validating against the external internet.
rt := http.DefaultTransport
if inClusterDiscovery {
// If in-cluster discovery, then use the in-cluster config so we can
// authenticate with the API server.
cfg, err := rest.InClusterConfig()
if err != nil {
return nil, err
}
rt, err = rest.TransportFor(cfg)
if err != nil {
return nil, fmt.Errorf("could not get roundtripper: %v", err)
}
}
ctx := context.WithValue(context.Background(),
oauth2.HTTPClient, &http.Client{
Transport: rt,
})
return ctx, nil
}