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")))
|
||||
}
|
||||
|
||||
apiAuthenticator, err := authenticator.New(authenticator.AuthenticatorConfig{
|
||||
apiAuthenticator, securityDefinitions, err := authenticator.New(authenticator.AuthenticatorConfig{
|
||||
Anonymous: s.AnonymousAuth,
|
||||
AnyToken: s.EnableAnyToken,
|
||||
BasicAuthFile: s.BasicAuthFile,
|
||||
|
@ -309,6 +309,7 @@ func Run(s *options.APIServer) error {
|
|||
genericConfig.OpenAPIConfig.Definitions = generatedopenapi.OpenAPIDefinitions
|
||||
genericConfig.OpenAPIConfig.GetOperationID = openapi.GetOperationID
|
||||
genericConfig.EnableOpenAPISupport = true
|
||||
genericConfig.OpenAPIConfig.SecurityDefinitions = securityDefinitions
|
||||
|
||||
config := &master.Config{
|
||||
GenericConfig: genericConfig.Config,
|
||||
|
|
|
@ -117,7 +117,7 @@ func Run(s *options.ServerRunOptions) error {
|
|||
storageFactory.SetEtcdLocation(groupResource, servers)
|
||||
}
|
||||
|
||||
apiAuthenticator, err := authenticator.New(authenticator.AuthenticatorConfig{
|
||||
apiAuthenticator, securityDefinitions, err := authenticator.New(authenticator.AuthenticatorConfig{
|
||||
Anonymous: s.AnonymousAuth,
|
||||
AnyToken: s.EnableAnyToken,
|
||||
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.
|
||||
genericConfig.OpenAPIConfig.GetOperationID = apiserveropenapi.GetOperationID
|
||||
genericConfig.EnableOpenAPISupport = true
|
||||
genericConfig.OpenAPIConfig.SecurityDefinitions = securityDefinitions
|
||||
|
||||
// TODO: Move this to generic api server (Need to move the command line flag).
|
||||
if s.EnableWatchCache {
|
||||
|
|
|
@ -42,6 +42,7 @@ trap cleanup EXIT SIGINT
|
|||
|
||||
kube::golang::setup_env
|
||||
|
||||
TMP_DIR=$(mktemp -d /tmp/update-federation-openapi-spec.XXXX)
|
||||
ETCD_HOST=${ETCD_HOST:-127.0.0.1}
|
||||
ETCD_PORT=${ETCD_PORT:-2379}
|
||||
API_PORT=${API_PORT:-8050}
|
||||
|
@ -49,6 +50,8 @@ API_HOST=${API_HOST:-127.0.0.1}
|
|||
|
||||
kube::etcd::start
|
||||
|
||||
echo "dummy_token,admin,admin" > $TMP_DIR/tokenauth.csv
|
||||
|
||||
# Start federation-apiserver
|
||||
kube::log::status "Starting federation-apiserver"
|
||||
"${KUBE_OUTPUT_HOSTBIN}/hyperkube" federation-apiserver \
|
||||
|
@ -57,6 +60,7 @@ kube::log::status "Starting federation-apiserver"
|
|||
--insecure-port="${API_PORT}" \
|
||||
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
|
||||
--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 &
|
||||
APISERVER_PID=$!
|
||||
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
|
||||
|
||||
echo "dummy_token,admin,admin" > $TMP_DIR/tokenauth.csv
|
||||
|
||||
# Start kube-apiserver
|
||||
kube::log::status "Starting kube-apiserver"
|
||||
"${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \
|
||||
|
@ -61,6 +63,7 @@ kube::log::status "Starting kube-apiserver"
|
|||
--etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \
|
||||
--advertise-address="10.10.10.10" \
|
||||
--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 &
|
||||
APISERVER_PID=$!
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ package authenticator
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator/bearertoken"
|
||||
"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
|
||||
// Kubernetes authentication mechanisms.
|
||||
func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
||||
func New(config AuthenticatorConfig) (authenticator.Request, *spec.SecurityDefinitions, error) {
|
||||
var authenticators []authenticator.Request
|
||||
securityDefinitions := spec.SecurityDefinitions{}
|
||||
hasBasicAuth := false
|
||||
hasTokenAuth := false
|
||||
|
||||
// BasicAuth methods, local first, then remote
|
||||
if len(config.BasicAuthFile) > 0 {
|
||||
basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, basicAuth)
|
||||
hasBasicAuth = true
|
||||
}
|
||||
if len(config.KeystoneURL) > 0 {
|
||||
keystoneAuth, err := newAuthenticatorFromKeystoneURL(config.KeystoneURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, keystoneAuth)
|
||||
hasBasicAuth = true
|
||||
}
|
||||
|
||||
// X509 methods
|
||||
if len(config.ClientCAFile) > 0 {
|
||||
certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, certAuth)
|
||||
}
|
||||
|
@ -90,16 +97,18 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
|||
if len(config.TokenAuthFile) > 0 {
|
||||
tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, tokenAuth)
|
||||
hasTokenAuth = true
|
||||
}
|
||||
if len(config.ServiceAccountKeyFiles) > 0 {
|
||||
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, serviceAccountAuth)
|
||||
hasTokenAuth = true
|
||||
}
|
||||
// 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 {
|
||||
oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(config.OIDCIssuerURL, config.OIDCClientID, config.OIDCCAFile, config.OIDCUsernameClaim, config.OIDCGroupsClaim)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, oidcAuth)
|
||||
hasTokenAuth = true
|
||||
}
|
||||
if len(config.WebhookTokenAuthnConfigFile) > 0 {
|
||||
webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, webhookTokenAuth)
|
||||
hasTokenAuth = true
|
||||
}
|
||||
|
||||
// always add anytoken last, so that every other token authenticator gets to try first
|
||||
if config.AnyToken {
|
||||
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 config.Anonymous {
|
||||
return anonymous.NewAuthenticator(), nil
|
||||
return anonymous.NewAuthenticator(), &securityDefinitions, nil
|
||||
}
|
||||
}
|
||||
|
||||
switch len(authenticators) {
|
||||
case 0:
|
||||
return nil, nil
|
||||
return nil, &securityDefinitions, nil
|
||||
}
|
||||
|
||||
authenticator := union.New(authenticators...)
|
||||
|
@ -147,7 +179,7 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
|||
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
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -331,6 +332,28 @@ func (c *Config) Complete() completedConfig {
|
|||
}
|
||||
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}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,9 +45,15 @@ type Config struct {
|
|||
|
||||
// Info is general information about the API.
|
||||
Info *spec.Info
|
||||
|
||||
// DefaultResponse will be used if an operation does not have any responses listed. It
|
||||
// will show up as ... "responses" : {"default" : $DefaultResponse} in the spec.
|
||||
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
|
||||
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 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
|
||||
|
|
|
@ -78,10 +78,17 @@ func (o *openAPI) init(webServices []*restful.WebService) error {
|
|||
return r.Operation, nil
|
||||
}
|
||||
}
|
||||
if o.config.CommonResponses == nil {
|
||||
o.config.CommonResponses = map[int]spec.Response{}
|
||||
}
|
||||
err := o.buildPaths(webServices)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.config.SecurityDefinitions != nil {
|
||||
o.swagger.SecurityDefinitions = *o.config.SecurityDefinitions
|
||||
o.swagger.Security = o.config.DefaultSecurity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -227,6 +234,11 @@ func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map
|
|||
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 len(ret.Responses.StatusCodeResponses) == 0 {
|
||||
ret.Responses.Default = o.config.DefaultResponse
|
||||
|
|
Loading…
Reference in New Issue