mirror of https://github.com/k3s-io/k3s
Add authentication to openapi Spec
parent
eeae8b5975
commit
3e67cf8b9b
|
@ -216,7 +216,7 @@ func Run(s *options.APIServer) error {
|
||||||
serviceAccountGetter = serviceaccountcontroller.NewGetterFromStorageInterface(storageConfig, storageFactory.ResourcePrefix(api.Resource("serviceaccounts")), storageFactory.ResourcePrefix(api.Resource("secrets")))
|
serviceAccountGetter = serviceaccountcontroller.NewGetterFromStorageInterface(storageConfig, storageFactory.ResourcePrefix(api.Resource("serviceaccounts")), storageFactory.ResourcePrefix(api.Resource("secrets")))
|
||||||
}
|
}
|
||||||
|
|
||||||
apiAuthenticator, err := authenticator.New(authenticator.AuthenticatorConfig{
|
apiAuthenticator, securityDefinitions, err := authenticator.New(authenticator.AuthenticatorConfig{
|
||||||
Anonymous: s.AnonymousAuth,
|
Anonymous: s.AnonymousAuth,
|
||||||
AnyToken: s.EnableAnyToken,
|
AnyToken: s.EnableAnyToken,
|
||||||
BasicAuthFile: s.BasicAuthFile,
|
BasicAuthFile: s.BasicAuthFile,
|
||||||
|
@ -309,6 +309,7 @@ func Run(s *options.APIServer) error {
|
||||||
genericConfig.OpenAPIConfig.Definitions = generatedopenapi.OpenAPIDefinitions
|
genericConfig.OpenAPIConfig.Definitions = generatedopenapi.OpenAPIDefinitions
|
||||||
genericConfig.OpenAPIConfig.GetOperationID = openapi.GetOperationID
|
genericConfig.OpenAPIConfig.GetOperationID = openapi.GetOperationID
|
||||||
genericConfig.EnableOpenAPISupport = true
|
genericConfig.EnableOpenAPISupport = true
|
||||||
|
genericConfig.OpenAPIConfig.SecurityDefinitions = securityDefinitions
|
||||||
|
|
||||||
config := &master.Config{
|
config := &master.Config{
|
||||||
GenericConfig: genericConfig.Config,
|
GenericConfig: genericConfig.Config,
|
||||||
|
|
|
@ -117,7 +117,7 @@ func Run(s *options.ServerRunOptions) error {
|
||||||
storageFactory.SetEtcdLocation(groupResource, servers)
|
storageFactory.SetEtcdLocation(groupResource, servers)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiAuthenticator, err := authenticator.New(authenticator.AuthenticatorConfig{
|
apiAuthenticator, securityDefinitions, err := authenticator.New(authenticator.AuthenticatorConfig{
|
||||||
Anonymous: s.AnonymousAuth,
|
Anonymous: s.AnonymousAuth,
|
||||||
AnyToken: s.EnableAnyToken,
|
AnyToken: s.EnableAnyToken,
|
||||||
BasicAuthFile: s.BasicAuthFile,
|
BasicAuthFile: s.BasicAuthFile,
|
||||||
|
@ -200,6 +200,7 @@ func Run(s *options.ServerRunOptions) error {
|
||||||
// this method does not provide good operation IDs for federation, we should create federation's own GetOperationID.
|
// this method does not provide good operation IDs for federation, we should create federation's own GetOperationID.
|
||||||
genericConfig.OpenAPIConfig.GetOperationID = apiserveropenapi.GetOperationID
|
genericConfig.OpenAPIConfig.GetOperationID = apiserveropenapi.GetOperationID
|
||||||
genericConfig.EnableOpenAPISupport = true
|
genericConfig.EnableOpenAPISupport = true
|
||||||
|
genericConfig.OpenAPIConfig.SecurityDefinitions = securityDefinitions
|
||||||
|
|
||||||
// TODO: Move this to generic api server (Need to move the command line flag).
|
// TODO: Move this to generic api server (Need to move the command line flag).
|
||||||
if s.EnableWatchCache {
|
if s.EnableWatchCache {
|
||||||
|
|
|
@ -42,6 +42,7 @@ trap cleanup EXIT SIGINT
|
||||||
|
|
||||||
kube::golang::setup_env
|
kube::golang::setup_env
|
||||||
|
|
||||||
|
TMP_DIR=$(mktemp -d /tmp/update-federation-openapi-spec.XXXX)
|
||||||
ETCD_HOST=${ETCD_HOST:-127.0.0.1}
|
ETCD_HOST=${ETCD_HOST:-127.0.0.1}
|
||||||
ETCD_PORT=${ETCD_PORT:-2379}
|
ETCD_PORT=${ETCD_PORT:-2379}
|
||||||
API_PORT=${API_PORT:-8050}
|
API_PORT=${API_PORT:-8050}
|
||||||
|
@ -49,6 +50,8 @@ API_HOST=${API_HOST:-127.0.0.1}
|
||||||
|
|
||||||
kube::etcd::start
|
kube::etcd::start
|
||||||
|
|
||||||
|
echo "dummy_token,admin,admin" > $TMP_DIR/tokenauth.csv
|
||||||
|
|
||||||
# Start federation-apiserver
|
# Start federation-apiserver
|
||||||
kube::log::status "Starting federation-apiserver"
|
kube::log::status "Starting federation-apiserver"
|
||||||
"${KUBE_OUTPUT_HOSTBIN}/hyperkube" federation-apiserver \
|
"${KUBE_OUTPUT_HOSTBIN}/hyperkube" federation-apiserver \
|
||||||
|
@ -57,6 +60,7 @@ kube::log::status "Starting federation-apiserver"
|
||||||
--insecure-port="${API_PORT}" \
|
--insecure-port="${API_PORT}" \
|
||||||
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
|
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
|
||||||
--advertise-address="10.10.10.10" \
|
--advertise-address="10.10.10.10" \
|
||||||
|
--token-auth-file=$TMP_DIR/tokenauth.csv \
|
||||||
--service-cluster-ip-range="10.0.0.0/24" >/tmp/openapi-federation-api-server.log 2>&1 &
|
--service-cluster-ip-range="10.0.0.0/24" >/tmp/openapi-federation-api-server.log 2>&1 &
|
||||||
APISERVER_PID=$!
|
APISERVER_PID=$!
|
||||||
kube::util::wait_for_url "${API_HOST}:${API_PORT}/" "apiserver: "
|
kube::util::wait_for_url "${API_HOST}:${API_PORT}/" "apiserver: "
|
||||||
|
|
|
@ -52,6 +52,8 @@ API_HOST=${API_HOST:-127.0.0.1}
|
||||||
|
|
||||||
kube::etcd::start
|
kube::etcd::start
|
||||||
|
|
||||||
|
echo "dummy_token,admin,admin" > $TMP_DIR/tokenauth.csv
|
||||||
|
|
||||||
# Start kube-apiserver
|
# Start kube-apiserver
|
||||||
kube::log::status "Starting kube-apiserver"
|
kube::log::status "Starting kube-apiserver"
|
||||||
"${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \
|
"${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \
|
||||||
|
@ -61,6 +63,7 @@ kube::log::status "Starting kube-apiserver"
|
||||||
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
|
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
|
||||||
--advertise-address="10.10.10.10" \
|
--advertise-address="10.10.10.10" \
|
||||||
--cert-dir="${TMP_DIR}/certs" \
|
--cert-dir="${TMP_DIR}/certs" \
|
||||||
|
--token-auth-file=$TMP_DIR/tokenauth.csv \
|
||||||
--service-cluster-ip-range="10.0.0.0/24" >/tmp/openapi-api-server.log 2>&1 &
|
--service-cluster-ip-range="10.0.0.0/24" >/tmp/openapi-api-server.log 2>&1 &
|
||||||
APISERVER_PID=$!
|
APISERVER_PID=$!
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ package authenticator
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-openapi/spec"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||||
"k8s.io/kubernetes/pkg/auth/authenticator/bearertoken"
|
"k8s.io/kubernetes/pkg/auth/authenticator/bearertoken"
|
||||||
"k8s.io/kubernetes/pkg/auth/group"
|
"k8s.io/kubernetes/pkg/auth/group"
|
||||||
|
@ -58,30 +60,35 @@ type AuthenticatorConfig struct {
|
||||||
|
|
||||||
// New returns an authenticator.Request or an error that supports the standard
|
// New returns an authenticator.Request or an error that supports the standard
|
||||||
// Kubernetes authentication mechanisms.
|
// Kubernetes authentication mechanisms.
|
||||||
func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
func New(config AuthenticatorConfig) (authenticator.Request, *spec.SecurityDefinitions, error) {
|
||||||
var authenticators []authenticator.Request
|
var authenticators []authenticator.Request
|
||||||
|
securityDefinitions := spec.SecurityDefinitions{}
|
||||||
|
hasBasicAuth := false
|
||||||
|
hasTokenAuth := false
|
||||||
|
|
||||||
// BasicAuth methods, local first, then remote
|
// BasicAuth methods, local first, then remote
|
||||||
if len(config.BasicAuthFile) > 0 {
|
if len(config.BasicAuthFile) > 0 {
|
||||||
basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
|
basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, basicAuth)
|
authenticators = append(authenticators, basicAuth)
|
||||||
|
hasBasicAuth = true
|
||||||
}
|
}
|
||||||
if len(config.KeystoneURL) > 0 {
|
if len(config.KeystoneURL) > 0 {
|
||||||
keystoneAuth, err := newAuthenticatorFromKeystoneURL(config.KeystoneURL)
|
keystoneAuth, err := newAuthenticatorFromKeystoneURL(config.KeystoneURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, keystoneAuth)
|
authenticators = append(authenticators, keystoneAuth)
|
||||||
|
hasBasicAuth = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// X509 methods
|
// X509 methods
|
||||||
if len(config.ClientCAFile) > 0 {
|
if len(config.ClientCAFile) > 0 {
|
||||||
certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
|
certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, certAuth)
|
authenticators = append(authenticators, certAuth)
|
||||||
}
|
}
|
||||||
|
@ -90,16 +97,18 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
||||||
if len(config.TokenAuthFile) > 0 {
|
if len(config.TokenAuthFile) > 0 {
|
||||||
tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
|
tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, tokenAuth)
|
authenticators = append(authenticators, tokenAuth)
|
||||||
|
hasTokenAuth = true
|
||||||
}
|
}
|
||||||
if len(config.ServiceAccountKeyFiles) > 0 {
|
if len(config.ServiceAccountKeyFiles) > 0 {
|
||||||
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
|
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, serviceAccountAuth)
|
authenticators = append(authenticators, serviceAccountAuth)
|
||||||
|
hasTokenAuth = true
|
||||||
}
|
}
|
||||||
// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
|
// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
|
||||||
//
|
//
|
||||||
|
@ -110,32 +119,55 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
||||||
if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 {
|
if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 {
|
||||||
oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(config.OIDCIssuerURL, config.OIDCClientID, config.OIDCCAFile, config.OIDCUsernameClaim, config.OIDCGroupsClaim)
|
oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(config.OIDCIssuerURL, config.OIDCClientID, config.OIDCCAFile, config.OIDCUsernameClaim, config.OIDCGroupsClaim)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, oidcAuth)
|
authenticators = append(authenticators, oidcAuth)
|
||||||
|
hasTokenAuth = true
|
||||||
}
|
}
|
||||||
if len(config.WebhookTokenAuthnConfigFile) > 0 {
|
if len(config.WebhookTokenAuthnConfigFile) > 0 {
|
||||||
webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL)
|
webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
authenticators = append(authenticators, webhookTokenAuth)
|
authenticators = append(authenticators, webhookTokenAuth)
|
||||||
|
hasTokenAuth = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// always add anytoken last, so that every other token authenticator gets to try first
|
// always add anytoken last, so that every other token authenticator gets to try first
|
||||||
if config.AnyToken {
|
if config.AnyToken {
|
||||||
authenticators = append(authenticators, bearertoken.New(anytoken.AnyTokenAuthenticator{}))
|
authenticators = append(authenticators, bearertoken.New(anytoken.AnyTokenAuthenticator{}))
|
||||||
|
hasTokenAuth = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasBasicAuth {
|
||||||
|
securityDefinitions["HTTPBasic"] = &spec.SecurityScheme{
|
||||||
|
SecuritySchemeProps: spec.SecuritySchemeProps{
|
||||||
|
Type: "basic",
|
||||||
|
Description: "HTTP Basic authentication",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasTokenAuth {
|
||||||
|
securityDefinitions["BearerToken"] = &spec.SecurityScheme{
|
||||||
|
SecuritySchemeProps: spec.SecuritySchemeProps{
|
||||||
|
Type: "apiKey",
|
||||||
|
Name: "authorization",
|
||||||
|
In: "header",
|
||||||
|
Description: "Bearer Token authentication",
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(authenticators) == 0 {
|
if len(authenticators) == 0 {
|
||||||
if config.Anonymous {
|
if config.Anonymous {
|
||||||
return anonymous.NewAuthenticator(), nil
|
return anonymous.NewAuthenticator(), &securityDefinitions, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch len(authenticators) {
|
switch len(authenticators) {
|
||||||
case 0:
|
case 0:
|
||||||
return nil, nil
|
return nil, &securityDefinitions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticator := union.New(authenticators...)
|
authenticator := union.New(authenticators...)
|
||||||
|
@ -147,7 +179,7 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
||||||
authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
|
authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
|
||||||
}
|
}
|
||||||
|
|
||||||
return authenticator, nil
|
return authenticator, &securityDefinitions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
|
// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -331,6 +332,28 @@ func (c *Config) Complete() completedConfig {
|
||||||
}
|
}
|
||||||
c.ExternalHost = hostAndPort
|
c.ExternalHost = hostAndPort
|
||||||
}
|
}
|
||||||
|
// All APIs will have the same authentication for now.
|
||||||
|
if c.OpenAPIConfig != nil && c.OpenAPIConfig.SecurityDefinitions != nil {
|
||||||
|
c.OpenAPIConfig.DefaultSecurity = []map[string][]string{}
|
||||||
|
keys := []string{}
|
||||||
|
for k := range *c.OpenAPIConfig.SecurityDefinitions {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, k := range keys {
|
||||||
|
c.OpenAPIConfig.DefaultSecurity = append(c.OpenAPIConfig.DefaultSecurity, map[string][]string{k: {}})
|
||||||
|
}
|
||||||
|
if c.OpenAPIConfig.CommonResponses == nil {
|
||||||
|
c.OpenAPIConfig.CommonResponses = map[int]spec.Response{}
|
||||||
|
}
|
||||||
|
if _, exists := c.OpenAPIConfig.CommonResponses[http.StatusUnauthorized]; !exists {
|
||||||
|
c.OpenAPIConfig.CommonResponses[http.StatusUnauthorized] = spec.Response{
|
||||||
|
ResponseProps: spec.ResponseProps{
|
||||||
|
Description: "Unauthorized",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return completedConfig{c}
|
return completedConfig{c}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,15 @@ type Config struct {
|
||||||
|
|
||||||
// Info is general information about the API.
|
// Info is general information about the API.
|
||||||
Info *spec.Info
|
Info *spec.Info
|
||||||
|
|
||||||
// DefaultResponse will be used if an operation does not have any responses listed. It
|
// DefaultResponse will be used if an operation does not have any responses listed. It
|
||||||
// will show up as ... "responses" : {"default" : $DefaultResponse} in the spec.
|
// will show up as ... "responses" : {"default" : $DefaultResponse} in the spec.
|
||||||
DefaultResponse *spec.Response
|
DefaultResponse *spec.Response
|
||||||
|
|
||||||
|
// CommonResponses will be added as a response to all operation specs. This is a good place to add common
|
||||||
|
// responses such as authorization failed.
|
||||||
|
CommonResponses map[int]spec.Response
|
||||||
|
|
||||||
// List of webservice's path prefixes to ignore
|
// List of webservice's path prefixes to ignore
|
||||||
IgnorePrefixes []string
|
IgnorePrefixes []string
|
||||||
|
|
||||||
|
@ -57,6 +63,14 @@ type Config struct {
|
||||||
|
|
||||||
// GetOperationID returns operation id for a restful route. It is an optional function to customize operation IDs.
|
// GetOperationID returns operation id for a restful route. It is an optional function to customize operation IDs.
|
||||||
GetOperationID func(servePath string, r *restful.Route) (string, error)
|
GetOperationID func(servePath string, r *restful.Route) (string, error)
|
||||||
|
|
||||||
|
// SecurityDefinitions is list of all security definitions for OpenAPI service. If this is not nil, the user of config
|
||||||
|
// is responsible to provide DefaultSecurity and (maybe) add unauthorized response to CommonResponses.
|
||||||
|
SecurityDefinitions *spec.SecurityDefinitions
|
||||||
|
|
||||||
|
// DefaultSecurity for all operations. This will pass as spec.SwaggerProps.Security to OpenAPI.
|
||||||
|
// For most cases, this will be list of acceptable definitions in SecurityDefinitions.
|
||||||
|
DefaultSecurity []map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is a reference for converting go (or any custom type) to a simple open API type,format pair. There are
|
// This function is a reference for converting go (or any custom type) to a simple open API type,format pair. There are
|
||||||
|
|
|
@ -78,10 +78,17 @@ func (o *openAPI) init(webServices []*restful.WebService) error {
|
||||||
return r.Operation, nil
|
return r.Operation, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if o.config.CommonResponses == nil {
|
||||||
|
o.config.CommonResponses = map[int]spec.Response{}
|
||||||
|
}
|
||||||
err := o.buildPaths(webServices)
|
err := o.buildPaths(webServices)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if o.config.SecurityDefinitions != nil {
|
||||||
|
o.swagger.SecurityDefinitions = *o.config.SecurityDefinitions
|
||||||
|
o.swagger.Security = o.config.DefaultSecurity
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,6 +234,11 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for code, resp := range o.config.CommonResponses {
|
||||||
|
if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
|
||||||
|
ret.Responses.StatusCodeResponses[code] = resp
|
||||||
|
}
|
||||||
|
}
|
||||||
// If there is still no response, use default response provided.
|
// If there is still no response, use default response provided.
|
||||||
if len(ret.Responses.StatusCodeResponses) == 0 {
|
if len(ret.Responses.StatusCodeResponses) == 0 {
|
||||||
ret.Responses.Default = o.config.DefaultResponse
|
ret.Responses.Default = o.config.DefaultResponse
|
||||||
|
|
Loading…
Reference in New Issue