mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Merge pull request #88049 from mtaufen/provider-info-agnhost
Update agnhost to test OIDC validation of JWT tokens
This commit is contained in:
commit
f692f5cfcd
1
go.mod
1
go.mod
@ -38,6 +38,7 @@ require (
|
|||||||
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20 // indirect
|
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20 // indirect
|
||||||
github.com/containernetworking/cni v0.7.1
|
github.com/containernetworking/cni v0.7.1
|
||||||
github.com/coredns/corefile-migration v1.0.6
|
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/go-systemd v0.0.0-20190321100706-95778dfbb74e
|
||||||
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea
|
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea
|
||||||
github.com/cpuguy83/go-md2man v1.0.10
|
github.com/cpuguy83/go-md2man v1.0.10
|
||||||
|
@ -32,6 +32,7 @@ go_library(
|
|||||||
"//test/images/agnhost/nettest:go_default_library",
|
"//test/images/agnhost/nettest:go_default_library",
|
||||||
"//test/images/agnhost/no-snat-test: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/no-snat-test-proxy:go_default_library",
|
||||||
|
"//test/images/agnhost/openidmetadata:go_default_library",
|
||||||
"//test/images/agnhost/pause:go_default_library",
|
"//test/images/agnhost/pause:go_default_library",
|
||||||
"//test/images/agnhost/port-forward-tester:go_default_library",
|
"//test/images/agnhost/port-forward-tester:go_default_library",
|
||||||
"//test/images/agnhost/porter:go_default_library",
|
"//test/images/agnhost/porter:go_default_library",
|
||||||
@ -71,6 +72,7 @@ filegroup(
|
|||||||
"//test/images/agnhost/nettest:all-srcs",
|
"//test/images/agnhost/nettest:all-srcs",
|
||||||
"//test/images/agnhost/no-snat-test:all-srcs",
|
"//test/images/agnhost/no-snat-test:all-srcs",
|
||||||
"//test/images/agnhost/no-snat-test-proxy: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/pause:all-srcs",
|
||||||
"//test/images/agnhost/port-forward-tester:all-srcs",
|
"//test/images/agnhost/port-forward-tester:all-srcs",
|
||||||
"//test/images/agnhost/porter:all-srcs",
|
"//test/images/agnhost/porter:all-srcs",
|
||||||
|
@ -1 +1 @@
|
|||||||
2.11
|
2.12
|
||||||
|
@ -22,33 +22,34 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"k8s.io/klog"
|
"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/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/dns"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/entrypoint-tester"
|
"k8s.io/kubernetes/test/images/agnhost/entrypoint-tester"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/fakegitserver"
|
"k8s.io/kubernetes/test/images/agnhost/fakegitserver"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/guestbook"
|
"k8s.io/kubernetes/test/images/agnhost/guestbook"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/inclusterclient"
|
"k8s.io/kubernetes/test/images/agnhost/inclusterclient"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/liveness"
|
"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/mounttest"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/net"
|
"k8s.io/kubernetes/test/images/agnhost/net"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/netexec"
|
"k8s.io/kubernetes/test/images/agnhost/netexec"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/nettest"
|
"k8s.io/kubernetes/test/images/agnhost/nettest"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/no-snat-test"
|
nosnat "k8s.io/kubernetes/test/images/agnhost/no-snat-test"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/no-snat-test-proxy"
|
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/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/porter"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/resource-consumer-controller"
|
resconsumerctrl "k8s.io/kubernetes/test/images/agnhost/resource-consumer-controller"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/serve-hostname"
|
servehostname "k8s.io/kubernetes/test/images/agnhost/serve-hostname"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/test-webserver"
|
testwebserver "k8s.io/kubernetes/test/images/agnhost/test-webserver"
|
||||||
"k8s.io/kubernetes/test/images/agnhost/webhook"
|
"k8s.io/kubernetes/test/images/agnhost/webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rootCmd := &cobra.Command{Use: "app", Version: "2.11"}
|
rootCmd := &cobra.Command{Use: "app", Version: "2.12"}
|
||||||
|
|
||||||
rootCmd.AddCommand(auditproxy.CmdAuditProxy)
|
rootCmd.AddCommand(auditproxy.CmdAuditProxy)
|
||||||
rootCmd.AddCommand(connect.CmdConnect)
|
rootCmd.AddCommand(connect.CmdConnect)
|
||||||
@ -75,6 +76,7 @@ func main() {
|
|||||||
rootCmd.AddCommand(servehostname.CmdServeHostname)
|
rootCmd.AddCommand(servehostname.CmdServeHostname)
|
||||||
rootCmd.AddCommand(testwebserver.CmdTestWebserver)
|
rootCmd.AddCommand(testwebserver.CmdTestWebserver)
|
||||||
rootCmd.AddCommand(webhook.CmdWebhook)
|
rootCmd.AddCommand(webhook.CmdWebhook)
|
||||||
|
rootCmd.AddCommand(openidmetadata.CmdTestServiceAccountIssuerDiscovery)
|
||||||
|
|
||||||
// NOTE(claudiub): Some tests are passing logging related flags, so we need to be able to
|
// 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.
|
// accept them. This will also include them in the printed help.
|
||||||
|
29
test/images/agnhost/openidmetadata/BUILD
Normal file
29
test/images/agnhost/openidmetadata/BUILD
Normal 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"],
|
||||||
|
)
|
164
test/images/agnhost/openidmetadata/openidmetadata.go
Normal file
164
test/images/agnhost/openidmetadata/openidmetadata.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user