Add authentication to openapi Spec

pull/6/head
mbohlool 2016-10-17 21:48:17 -07:00
parent eeae8b5975
commit 3e67cf8b9b
8 changed files with 103 additions and 13 deletions

View File

@ -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,

View File

@ -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 {

View File

@ -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: "

View File

@ -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=$!

View File

@ -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

View 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}
}

View File

@ -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

View File

@ -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