mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 10:51:29 +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/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
|
||||
|
@ -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",
|
||||
|
@ -1 +1 @@
|
||||
2.11
|
||||
2.12
|
||||
|
@ -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.
|
||||
|
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