未验证 提交 9b9d4021 编写于 作者: H hongming

implement identity provider and built-in oauth server

Signed-off-by: Nhongming <talonwan@yunify.com>
上级 59002cd1
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
kubesphereconfig "kubesphere.io/kubesphere/pkg/apiserver/config" kubesphereconfig "kubesphere.io/kubesphere/pkg/apiserver/config"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins" "kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/k8s" "kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/kubesphere"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix" "kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
"kubesphere.io/kubesphere/pkg/simple/client/s3" "kubesphere.io/kubesphere/pkg/simple/client/s3"
"strings" "strings"
...@@ -21,9 +20,7 @@ type KubeSphereControllerManagerOptions struct { ...@@ -21,9 +20,7 @@ type KubeSphereControllerManagerOptions struct {
DevopsOptions *jenkins.Options DevopsOptions *jenkins.Options
S3Options *s3.Options S3Options *s3.Options
OpenPitrixOptions *openpitrix.Options OpenPitrixOptions *openpitrix.Options
KubeSphereOptions *kubesphere.Options LeaderElection *leaderelection.LeaderElectionConfig
LeaderElection *leaderelection.LeaderElectionConfig
} }
func NewKubeSphereControllerManagerOptions() *KubeSphereControllerManagerOptions { func NewKubeSphereControllerManagerOptions() *KubeSphereControllerManagerOptions {
...@@ -32,7 +29,6 @@ func NewKubeSphereControllerManagerOptions() *KubeSphereControllerManagerOptions ...@@ -32,7 +29,6 @@ func NewKubeSphereControllerManagerOptions() *KubeSphereControllerManagerOptions
DevopsOptions: jenkins.NewDevopsOptions(), DevopsOptions: jenkins.NewDevopsOptions(),
S3Options: s3.NewS3Options(), S3Options: s3.NewS3Options(),
OpenPitrixOptions: openpitrix.NewOptions(), OpenPitrixOptions: openpitrix.NewOptions(),
KubeSphereOptions: kubesphere.NewKubeSphereOptions(),
LeaderElection: &leaderelection.LeaderElectionConfig{ LeaderElection: &leaderelection.LeaderElectionConfig{
LeaseDuration: 30 * time.Second, LeaseDuration: 30 * time.Second,
RenewDeadline: 15 * time.Second, RenewDeadline: 15 * time.Second,
......
...@@ -38,7 +38,6 @@ import ( ...@@ -38,7 +38,6 @@ import (
"kubesphere.io/kubesphere/pkg/controller/workspace" "kubesphere.io/kubesphere/pkg/controller/workspace"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/simple/client/k8s" "kubesphere.io/kubesphere/pkg/simple/client/k8s"
kclient "kubesphere.io/kubesphere/pkg/simple/client/kubesphere"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix" "kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
"kubesphere.io/kubesphere/pkg/utils/term" "kubesphere.io/kubesphere/pkg/utils/term"
"os" "os"
...@@ -96,8 +95,6 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) ...@@ -96,8 +95,6 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{})
return err return err
} }
kubesphereClient := kclient.NewKubeSphereClient(s.KubeSphereOptions)
openpitrixClient, err := openpitrix.NewClient(s.OpenPitrixOptions) openpitrixClient, err := openpitrix.NewClient(s.OpenPitrixOptions)
if err != nil { if err != nil {
klog.Errorf("Failed to create openpitrix client %v", err) klog.Errorf("Failed to create openpitrix client %v", err)
...@@ -136,7 +133,7 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) ...@@ -136,7 +133,7 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{})
} }
klog.V(0).Info("Setting up controllers") klog.V(0).Info("Setting up controllers")
err = workspace.Add(mgr, kubesphereClient) err = workspace.Add(mgr)
if err != nil { if err != nil {
klog.Fatal("Unable to create workspace controller") klog.Fatal("Unable to create workspace controller")
} }
......
...@@ -6,8 +6,9 @@ import ( ...@@ -6,8 +6,9 @@ import (
"fmt" "fmt"
cliflag "k8s.io/component-base/cli/flag" cliflag "k8s.io/component-base/cli/flag"
"k8s.io/klog" "k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api/auth"
"kubesphere.io/kubesphere/pkg/apiserver" "kubesphere.io/kubesphere/pkg/apiserver"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
apiserverconfig "kubesphere.io/kubesphere/pkg/apiserver/config"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/informers"
genericoptions "kubesphere.io/kubesphere/pkg/server/options" genericoptions "kubesphere.io/kubesphere/pkg/server/options"
"kubesphere.io/kubesphere/pkg/simple/client/cache" "kubesphere.io/kubesphere/pkg/simple/client/cache"
...@@ -29,42 +30,32 @@ import ( ...@@ -29,42 +30,32 @@ import (
type ServerRunOptions struct { type ServerRunOptions struct {
ConfigFile string ConfigFile string
GenericServerRunOptions *genericoptions.ServerRunOptions GenericServerRunOptions *genericoptions.ServerRunOptions
KubernetesOptions *k8s.KubernetesOptions *apiserverconfig.Config
DevopsOptions *jenkins.Options
SonarQubeOptions *sonarqube.Options
ServiceMeshOptions *servicemesh.Options
MySQLOptions *mysql.Options
MonitoringOptions *prometheus.Options
S3Options *s3.Options
OpenPitrixOptions *openpitrix.Options
LoggingOptions *esclient.Options
LdapOptions *ldap.Options
CacheOptions *cache.Options
AuthenticateOptions *auth.AuthenticationOptions
// //
DebugMode bool DebugMode bool
} }
func NewServerRunOptions() *ServerRunOptions { func NewServerRunOptions() *ServerRunOptions {
s := &ServerRunOptions{
s := ServerRunOptions{
GenericServerRunOptions: genericoptions.NewServerRunOptions(), GenericServerRunOptions: genericoptions.NewServerRunOptions(),
KubernetesOptions: k8s.NewKubernetesOptions(), Config: &apiserverconfig.Config{
DevopsOptions: jenkins.NewDevopsOptions(), KubernetesOptions: k8s.NewKubernetesOptions(),
SonarQubeOptions: sonarqube.NewSonarQubeOptions(), DevopsOptions: jenkins.NewDevopsOptions(),
ServiceMeshOptions: servicemesh.NewServiceMeshOptions(), SonarQubeOptions: sonarqube.NewSonarQubeOptions(),
MySQLOptions: mysql.NewMySQLOptions(), ServiceMeshOptions: servicemesh.NewServiceMeshOptions(),
MonitoringOptions: prometheus.NewPrometheusOptions(), MySQLOptions: mysql.NewMySQLOptions(),
S3Options: s3.NewS3Options(), MonitoringOptions: prometheus.NewPrometheusOptions(),
OpenPitrixOptions: openpitrix.NewOptions(), S3Options: s3.NewS3Options(),
LoggingOptions: esclient.NewElasticSearchOptions(), OpenPitrixOptions: openpitrix.NewOptions(),
LdapOptions: ldap.NewOptions(), LoggingOptions: esclient.NewElasticSearchOptions(),
CacheOptions: cache.NewRedisOptions(), LdapOptions: ldap.NewOptions(),
AuthenticateOptions: auth.NewAuthenticateOptions(), RedisOptions: cache.NewRedisOptions(),
AuthenticationOptions: authoptions.NewAuthenticateOptions(),
},
} }
return &s return s
} }
func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) { func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) {
...@@ -72,12 +63,12 @@ func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) { ...@@ -72,12 +63,12 @@ func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) {
fs.BoolVar(&s.DebugMode, "debug", false, "Don't enable this if you don't know what it means.") fs.BoolVar(&s.DebugMode, "debug", false, "Don't enable this if you don't know what it means.")
s.GenericServerRunOptions.AddFlags(fs, s.GenericServerRunOptions) s.GenericServerRunOptions.AddFlags(fs, s.GenericServerRunOptions)
s.KubernetesOptions.AddFlags(fss.FlagSet("kubernetes"), s.KubernetesOptions) s.KubernetesOptions.AddFlags(fss.FlagSet("kubernetes"), s.KubernetesOptions)
s.AuthenticateOptions.AddFlags(fss.FlagSet("authenticate"), s.AuthenticateOptions) s.AuthenticationOptions.AddFlags(fss.FlagSet("authentication"), s.AuthenticationOptions)
s.MySQLOptions.AddFlags(fss.FlagSet("mysql"), s.MySQLOptions) s.MySQLOptions.AddFlags(fss.FlagSet("mysql"), s.MySQLOptions)
s.DevopsOptions.AddFlags(fss.FlagSet("devops"), s.DevopsOptions) s.DevopsOptions.AddFlags(fss.FlagSet("devops"), s.DevopsOptions)
s.SonarQubeOptions.AddFlags(fss.FlagSet("sonarqube"), s.SonarQubeOptions) s.SonarQubeOptions.AddFlags(fss.FlagSet("sonarqube"), s.SonarQubeOptions)
s.LdapOptions.AddFlags(fss.FlagSet("ldap"), s.LdapOptions) s.LdapOptions.AddFlags(fss.FlagSet("ldap"), s.LdapOptions)
s.CacheOptions.AddFlags(fss.FlagSet("cache"), s.CacheOptions) s.RedisOptions.AddFlags(fss.FlagSet("redis"), s.RedisOptions)
s.S3Options.AddFlags(fss.FlagSet("s3"), s.S3Options) s.S3Options.AddFlags(fss.FlagSet("s3"), s.S3Options)
s.OpenPitrixOptions.AddFlags(fss.FlagSet("openpitrix"), s.OpenPitrixOptions) s.OpenPitrixOptions.AddFlags(fss.FlagSet("openpitrix"), s.OpenPitrixOptions)
s.ServiceMeshOptions.AddFlags(fss.FlagSet("servicemesh"), s.ServiceMeshOptions) s.ServiceMeshOptions.AddFlags(fss.FlagSet("servicemesh"), s.ServiceMeshOptions)
...@@ -100,7 +91,7 @@ const fakeInterface string = "FAKE" ...@@ -100,7 +91,7 @@ const fakeInterface string = "FAKE"
// NewAPIServer creates an APIServer instance using given options // NewAPIServer creates an APIServer instance using given options
func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIServer, error) { func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIServer, error) {
apiServer := &apiserver.APIServer{ apiServer := &apiserver.APIServer{
AuthenticateOptions: s.AuthenticateOptions, Config: s.Config,
} }
kubernetesClient, err := k8s.NewKubernetesClient(s.KubernetesOptions) kubernetesClient, err := k8s.NewKubernetesClient(s.KubernetesOptions)
...@@ -156,11 +147,11 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS ...@@ -156,11 +147,11 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS
} }
var cacheClient cache.Interface var cacheClient cache.Interface
if s.CacheOptions.Host != "" { if s.RedisOptions.Host != "" {
if s.CacheOptions.Host == fakeInterface && s.DebugMode { if s.RedisOptions.Host == fakeInterface && s.DebugMode {
apiServer.CacheClient = cache.NewSimpleCache() apiServer.CacheClient = cache.NewSimpleCache()
} else { } else {
cacheClient, err = cache.NewRedisClient(s.CacheOptions, stopCh) cacheClient, err = cache.NewRedisClient(s.RedisOptions, stopCh)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -37,18 +37,7 @@ func NewAPIServerCommand() *cobra.Command { ...@@ -37,18 +37,7 @@ func NewAPIServerCommand() *cobra.Command {
if err == nil { if err == nil {
s = &options.ServerRunOptions{ s = &options.ServerRunOptions{
GenericServerRunOptions: s.GenericServerRunOptions, GenericServerRunOptions: s.GenericServerRunOptions,
KubernetesOptions: conf.KubernetesOptions, Config: conf,
DevopsOptions: conf.DevopsOptions,
SonarQubeOptions: conf.SonarQubeOptions,
ServiceMeshOptions: conf.ServiceMeshOptions,
MySQLOptions: conf.MySQLOptions,
MonitoringOptions: conf.MonitoringOptions,
S3Options: conf.S3Options,
OpenPitrixOptions: conf.OpenPitrixOptions,
LoggingOptions: conf.LoggingOptions,
LdapOptions: conf.LdapOptions,
CacheOptions: conf.RedisOptions,
AuthenticateOptions: conf.AuthenticateOptions,
} }
} }
......
...@@ -12,31 +12,29 @@ import ( ...@@ -12,31 +12,29 @@ import (
unionauth "k8s.io/apiserver/pkg/authentication/request/union" unionauth "k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/klog" "k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api/auth"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/basic" "kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/basic"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken" "kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken"
oauth2 "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/anonymous" "kubesphere.io/kubesphere/pkg/apiserver/authentication/request/anonymous"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/basictoken" "kubesphere.io/kubesphere/pkg/apiserver/authentication/request/basictoken"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token" "kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory" "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/path" "kubesphere.io/kubesphere/pkg/apiserver/authorization/path"
unionauthorizer "kubesphere.io/kubesphere/pkg/apiserver/authorization/union" unionauthorizer "kubesphere.io/kubesphere/pkg/apiserver/authorization/union"
apiserverconfig "kubesphere.io/kubesphere/pkg/apiserver/config"
"kubesphere.io/kubesphere/pkg/apiserver/dispatch" "kubesphere.io/kubesphere/pkg/apiserver/dispatch"
"kubesphere.io/kubesphere/pkg/apiserver/filters" "kubesphere.io/kubesphere/pkg/apiserver/filters"
"kubesphere.io/kubesphere/pkg/apiserver/request" "kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/informers"
devopsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/devops/v1alpha2"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2" iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2"
loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2" loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2"
monitoringv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha2" monitoringv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha2"
"kubesphere.io/kubesphere/pkg/kapis/oauth" oauth "kubesphere.io/kubesphere/pkg/kapis/oauth"
openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1" openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1"
operationsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/operations/v1alpha2" operationsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/operations/v1alpha2"
resourcesv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha2" resourcesv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha2"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha3" resourcev1alpha3 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha3"
"kubesphere.io/kubesphere/pkg/kapis/serverconfig"
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2" servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2"
tenantv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2"
terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2" terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/iam/im" "kubesphere.io/kubesphere/pkg/models/iam/im"
...@@ -75,7 +73,7 @@ type APIServer struct { ...@@ -75,7 +73,7 @@ type APIServer struct {
// //
Server *http.Server Server *http.Server
AuthenticateOptions *auth.AuthenticationOptions Config *apiserverconfig.Config
// webservice container, where all webservice defines // webservice container, where all webservice defines
container *restful.Container container *restful.Container
...@@ -135,19 +133,19 @@ func (s *APIServer) PrepareRun() error { ...@@ -135,19 +133,19 @@ func (s *APIServer) PrepareRun() error {
} }
func (s *APIServer) installKubeSphereAPIs() { func (s *APIServer) installKubeSphereAPIs() {
urlruntime.Must(serverconfig.AddToContainer(s.container, s.Config))
urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory)) urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory))
// Need to refactor devops api registration, too much dependencies // Need to refactor devops api registration, too much dependencies
urlruntime.Must(devopsv1alpha2.AddToContainer(s.container, s.DevopsClient, s.DBClient.Database(), nil, s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory(), s.S3Client)) //urlruntime.Must(devopsv1alpha2.AddToContainer(s.container, s.DevopsClient, s.DBClient.Database(), nil, s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory(), s.S3Client))
urlruntime.Must(loggingv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.LoggingClient)) urlruntime.Must(loggingv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.LoggingClient))
urlruntime.Must(monitoringv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.MonitoringClient)) urlruntime.Must(monitoringv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.MonitoringClient))
urlruntime.Must(openpitrixv1.AddToContainer(s.container, s.InformerFactory, s.OpenpitrixClient)) urlruntime.Must(openpitrixv1.AddToContainer(s.container, s.InformerFactory, s.OpenpitrixClient))
urlruntime.Must(operationsv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes())) urlruntime.Must(operationsv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes()))
urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory)) urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory))
urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database())) //urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database()))
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config())) urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.AuthenticateOptions)) urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.Config.AuthenticationOptions))
urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.AuthenticateOptions, s.CacheClient), &oauth2.SimpleConfigManager{})) urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient), s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container)) urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
} }
...@@ -187,7 +185,7 @@ func (s *APIServer) buildHandlerChain() { ...@@ -187,7 +185,7 @@ func (s *APIServer) buildHandlerChain() {
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{}) handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})
handler = filters.WithMultipleClusterDispatcher(handler, dispatch.DefaultClusterDispatch) handler = filters.WithMultipleClusterDispatcher(handler, dispatch.DefaultClusterDispatch)
excludedPaths := []string{"/oauth/authorize", "/oauth/token"} excludedPaths := []string{"/oauth/*", "/server/configs/*"}
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths) pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
authorizer := unionauthorizer.New(pathAuthorizer, authorizer := unionauthorizer.New(pathAuthorizer,
authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator())) authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator()))
...@@ -196,7 +194,7 @@ func (s *APIServer) buildHandlerChain() { ...@@ -196,7 +194,7 @@ func (s *APIServer) buildHandlerChain() {
authn := unionauth.New(anonymous.NewAuthenticator(), authn := unionauth.New(anonymous.NewAuthenticator(),
basictoken.New(basic.NewBasicAuthenticator(im.NewFakeOperator())), basictoken.New(basic.NewBasicAuthenticator(im.NewFakeOperator())),
bearertoken.New(jwttoken.NewTokenAuthenticator( bearertoken.New(jwttoken.NewTokenAuthenticator(
token.NewJwtTokenIssuer(token.DefaultIssuerName, s.AuthenticateOptions, s.CacheClient)))) token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient))))
handler = filters.WithAuthentication(handler, authn) handler = filters.WithAuthentication(handler, authn)
handler = filters.WithRequestInfo(handler, requestInfoResolver) handler = filters.WithRequestInfo(handler, requestInfoResolver)
s.Server.Handler = handler s.Server.Handler = handler
......
...@@ -23,7 +23,7 @@ func NewTokenAuthenticator(issuer token2.Issuer) authenticator.Token { ...@@ -23,7 +23,7 @@ func NewTokenAuthenticator(issuer token2.Issuer) authenticator.Token {
} }
func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) { func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
providedUser, _, err := t.jwtTokenIssuer.Verify(token) providedUser, err := t.jwtTokenIssuer.Verify(token)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
......
...@@ -16,15 +16,10 @@ ...@@ -16,15 +16,10 @@
* / * /
*/ */
package oauth package identityprovider
import ( import "k8s.io/apiserver/pkg/authentication/user"
"errors"
"golang.org/x/oauth2"
)
var ConfigNotFound = errors.New("config not found") type OAuth2Interface interface {
IdentityExchange(code string) (user.Info, error)
type Configuration interface {
Load(clientId string) (*oauth2.Config, error)
} }
/*
*
* Copyright 2020 The KubeSphere 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 oauth
import (
"context"
"encoding/json"
"golang.org/x/oauth2"
"io/ioutil"
"k8s.io/apiserver/pkg/authentication/user"
"time"
)
const (
UserInfoURL = "https://api.github.com/user"
)
type Github struct {
// ClientID is the application's ID.
ClientID string `json:"clientID" yaml:"clientID"`
// ClientSecret is the application's secret.
ClientSecret string `json:"-" yaml:"clientSecret"`
// Endpoint contains the resource server's token endpoint
// URLs. These are constants specific to each server and are
// often available via site-specific packages, such as
// google.Endpoint or github.Endpoint.
Endpoint Endpoint `json:"endpoint" yaml:"endpoint"`
// RedirectURL is the URL to redirect users going through
// the OAuth flow, after the resource owner's URLs.
RedirectURL string `json:"redirectURL" yaml:"redirectURL"`
// Scope specifies optional requested permissions.
Scopes []string `json:"scopes" yaml:"scopes"`
}
// Endpoint represents an OAuth 2.0 provider's authorization and token
// endpoint URLs.
type Endpoint struct {
AuthURL string `json:"authURL" yaml:"authURL"`
TokenURL string `json:"tokenURL" yaml:"tokenURL"`
}
type GithubIdentity struct {
Login string `json:"login"`
ID string `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
Name string `json:"name"`
Company string `json:"company"`
Blog string `json:"blog"`
Location string `json:"location"`
Email string `json:"email"`
Hireable bool `json:"hireable"`
Bio string `json:"bio"`
PublicRepos int `json:"public_repos"`
PublicGists int `json:"public_gists"`
Followers int `json:"followers"`
Following int `json:"following"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
PrivateGists int `json:"private_gists"`
TotalPrivateRepos int `json:"total_private_repos"`
OwnedPrivateRepos int `json:"owned_private_repos"`
DiskUsage int `json:"disk_usage"`
Collaborators int `json:"collaborators"`
}
func (g GithubIdentity) GetName() string {
return g.Login
}
func (g GithubIdentity) GetUID() string {
return g.ID
}
func (g GithubIdentity) GetGroups() []string {
return nil
}
func (g GithubIdentity) GetExtra() map[string][]string {
return nil
}
func (g *Github) IdentityExchange(code string) (user.Info, error) {
config := oauth2.Config{
ClientID: g.ClientID,
ClientSecret: g.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: g.Endpoint.AuthURL,
TokenURL: g.Endpoint.TokenURL,
AuthStyle: oauth2.AuthStyleAutoDetect,
},
RedirectURL: g.RedirectURL,
Scopes: g.Scopes,
}
token, err := config.Exchange(context.Background(), code)
if err != nil {
return nil, err
}
resp, err := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(token)).Get(UserInfoURL)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
var githubIdentity GithubIdentity
err = json.Unmarshal(data, &githubIdentity)
if err != nil {
return nil, err
}
return githubIdentity, nil
}
/*
*
* Copyright 2020 The KubeSphere 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 oauth
import (
"errors"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
)
type GrantHandlerType string
type MappingMethod string
type IdentityProviderType string
const (
// GrantHandlerAuto auto-approves client authorization grant requests
GrantHandlerAuto GrantHandlerType = "auto"
// GrantHandlerPrompt prompts the user to approve new client authorization grant requests
GrantHandlerPrompt GrantHandlerType = "prompt"
// GrantHandlerDeny auto-denies client authorization grant requests
GrantHandlerDeny GrantHandlerType = "deny"
// MappingMethodAuto The default value.The user will automatically create and mapping when login successful.
// Fails if a user with that user name is already mapped to another identity.
MappingMethodAuto MappingMethod = "auto"
// MappingMethodLookup Looks up an existing identity, user identity mapping, and user, but does not automatically
// provision users or identities. Using this method requires you to manually provision users.
MappingMethodLookup MappingMethod = "lookup"
// MappingMethodMixed A user entity can be mapped with multiple identifyProvider.
MappingMethodMixed MappingMethod = "mixed"
IdentityProviderTypeGithub = "github"
)
var (
ErrorClientNotFound = errors.New("the OAuth client was not found")
ErrorRedirectURLNotAllowed = errors.New("redirect URL is not allowed")
ErrorIdentityProviderNotFound = errors.New("the identity provider was not found")
)
type Options struct {
// LDAPPasswordIdentityProvider provider is used by default.
IdentityProviders []IdentityProvider `json:"identityProviders,omitempty" yaml:"identityProviders,omitempty"`
// Register additional OAuth clients.
Clients []Client `json:"clients,omitempty" yaml:"clients,omitempty"`
// AccessTokenMaxAgeSeconds control the lifetime of access tokens. The default lifetime is 24 hours.
// 0 means no expiration.
AccessTokenMaxAgeSeconds int `json:"accessTokenMaxAgeSeconds" yaml:"accessTokenMaxAgeSeconds"`
// Inactivity timeout for tokens
// The value represents the maximum amount of time that can occur between
// consecutive uses of the token. Tokens become invalid if they are not
// used within this temporal window. The user will need to acquire a new
// token to regain access once a token times out.
// This value needs to be set only if the default set in configuration is
// not appropriate for this client. Valid values are:
// - 0: Tokens for this client never time out
// - X: Tokens time out if there is no activity for X seconds
// The current minimum allowed value for X is 300 (5 minutes)
AccessTokenInactivityTimeoutSeconds int `json:"accessTokenInactivityTimeoutSeconds" yaml:"accessTokenInactivityTimeoutSeconds"`
}
type IdentityProvider struct {
// The provider name.
Name string `json:"name" yaml:"name"`
// Defines how new identities are mapped to users when they login. Allowed values are:
// - auto: The default value.The user will automatically create and mapping when login successful.
// Fails if a user with that user name is already mapped to another identity.
// - lookup: Looks up an existing identity, user identity mapping, and user, but does not automatically
// provision users or identities. Using this method requires you to manually provision users.
// - mixed: A user entity can be mapped with multiple identifyProvider.
MappingMethod MappingMethod `json:"mappingMethod" yaml:"mappingMethod"`
// When true, unauthenticated token requests from web clients (like the web console)
// are redirected to a login page (with WWW-Authenticate challenge header) backed by this provider.
LoginRedirect bool `json:"loginRedirect" yaml:"loginRedirect"`
// The type of identify provider
Type string `json:"type" yaml:"type"`
// Provider options
Github *Github `json:"github" yaml:"github" `
}
type Token struct {
// AccessToken is the token that authorizes and authenticates
// the requests.
AccessToken string `json:"access_token"`
// TokenType is the type of token.
// The Type method returns either this or "Bearer", the default.
TokenType string `json:"token_type,omitempty"`
// RefreshToken is a token that's used by the application
// (as opposed to the user) to refresh the access token
// if it expires.
RefreshToken string `json:"refresh_token,omitempty"`
// ExpiresIn is the optional expiration second of the access token.
ExpiresIn int `json:"expires_in,omitempty"`
}
type Client struct {
// The name of the OAuth client is used as the client_id parameter when making requests to <master>/oauth/authorize
// and <master>/oauth/token.
Name string
// Secret is the unique secret associated with a client
Secret string `json:"-" yaml:"secret,omitempty"`
// RespondWithChallenges indicates whether the client wants authentication needed responses made
// in the form of challenges instead of redirects
RespondWithChallenges bool `json:"respondWithChallenges,omitempty" yaml:"respondWithChallenges,omitempty"`
// RedirectURIs is the valid redirection URIs associated with a client
RedirectURIs []string `json:"redirectURIs,omitempty" yaml:"redirectURIs,omitempty"`
// GrantMethod determines how to handle grants for this client. If no method is provided, the
// cluster default grant handling method will be used. Valid grant handling methods are:
// - auto: always approves grant requests, useful for trusted clients
// - prompt: prompts the end user for approval of grant requests, useful for third-party clients
// - deny: always denies grant requests, useful for black-listed clients
GrantMethod GrantHandlerType `json:"grantMethod,omitempty" yaml:"grantMethod,omitempty"`
// ScopeRestrictions describes which scopes this client can request. Each requested scope
// is checked against each restriction. If any restriction matches, then the scope is allowed.
// If no restriction matches, then the scope is denied.
ScopeRestrictions []string `json:"scopeRestrictions,omitempty" yaml:"scopeRestrictions,omitempty"`
// AccessTokenMaxAgeSeconds overrides the default access token max age for tokens granted to this client.
AccessTokenMaxAgeSeconds *int `json:"accessTokenMaxAgeSeconds,omitempty" yaml:"accessTokenMaxAgeSeconds,omitempty"`
// AccessTokenInactivityTimeoutSeconds overrides the default token
// inactivity timeout for tokens granted to this client.
AccessTokenInactivityTimeoutSeconds *int `json:"accessTokenInactivityTimeoutSeconds,omitempty" yaml:"accessTokenInactivityTimeoutSeconds,omitempty"`
}
func (o *Options) GetOAuthClient(name string) (Client, error) {
for _, found := range o.Clients {
if found.Name == name {
return found, nil
}
}
return Client{}, ErrorClientNotFound
}
func (o *Options) GetIdentityProvider(name string) (IdentityProvider, error) {
for _, found := range o.IdentityProviders {
if found.Name == name {
return found, nil
}
}
return IdentityProvider{}, ErrorClientNotFound
}
func (c Client) ResolveRedirectURL(expectURL string) (string, error) {
if len(c.RedirectURIs) == 0 {
return "", ErrorRedirectURLNotAllowed
}
if expectURL == "" {
return c.RedirectURIs[0], nil
}
if sliceutil.HasString(c.RedirectURIs, expectURL) {
return expectURL, nil
}
return "", ErrorRedirectURLNotAllowed
}
func (i IdentityProvider) GetOAuth2IdentityProviderInstance() (identityprovider.OAuth2Interface, error) {
switch i.Type {
case IdentityProviderTypeGithub:
if i.Github != nil {
return i.Github, nil
}
}
return nil, ErrorIdentityProviderNotFound
}
func NewOptions() *Options {
return &Options{
IdentityProviders: make([]IdentityProvider, 0),
Clients: make([]Client, 0),
AccessTokenMaxAgeSeconds: 86400,
AccessTokenInactivityTimeoutSeconds: 0,
}
}
...@@ -16,11 +16,12 @@ ...@@ -16,11 +16,12 @@
* / * /
*/ */
package auth package options
import ( import (
"fmt" "fmt"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"time" "time"
) )
...@@ -32,15 +33,13 @@ type AuthenticationOptions struct { ...@@ -32,15 +33,13 @@ type AuthenticationOptions struct {
// maximum retries when authenticate failed // maximum retries when authenticate failed
MaxAuthenticateRetries int `json:"maxAuthenticateRetries" yaml:"maxAuthenticateRetries"` MaxAuthenticateRetries int `json:"maxAuthenticateRetries" yaml:"maxAuthenticateRetries"`
// token validation duration, will refresh token expiration for each user request
// 0 means never expire
TokenExpiration time.Duration `json:"tokenExpiration" yaml:"tokenExpiration"`
// allow multiple users login at the same time // allow multiple users login at the same time
MultipleLogin bool `json:"multipleLogin" yaml:"multipleLogin"` MultipleLogin bool `json:"multipleLogin" yaml:"multipleLogin"`
// secret to signed jwt token // secret to signed jwt token
JwtSecret string `json:"jwtSecret" yaml:"jwtSecret"` JwtSecret string `json:"-" yaml:"jwtSecret"`
OAuthOptions *oauth.Options `json:"oauthOptions" yaml:"oauthOptions"`
} }
func NewAuthenticateOptions() *AuthenticationOptions { func NewAuthenticateOptions() *AuthenticationOptions {
...@@ -48,7 +47,7 @@ func NewAuthenticateOptions() *AuthenticationOptions { ...@@ -48,7 +47,7 @@ func NewAuthenticateOptions() *AuthenticationOptions {
AuthenticateRateLimiterMaxTries: 5, AuthenticateRateLimiterMaxTries: 5,
AuthenticateRateLimiterDuration: time.Minute * 30, AuthenticateRateLimiterDuration: time.Minute * 30,
MaxAuthenticateRetries: 0, MaxAuthenticateRetries: 0,
TokenExpiration: 0, OAuthOptions: oauth.NewOptions(),
MultipleLogin: false, MultipleLogin: false,
JwtSecret: "", JwtSecret: "",
} }
...@@ -68,7 +67,6 @@ func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *Authenticat ...@@ -68,7 +67,6 @@ func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *Authenticat
fs.IntVar(&options.AuthenticateRateLimiterMaxTries, "authenticate-rate-limiter-max-retries", s.AuthenticateRateLimiterMaxTries, "") fs.IntVar(&options.AuthenticateRateLimiterMaxTries, "authenticate-rate-limiter-max-retries", s.AuthenticateRateLimiterMaxTries, "")
fs.DurationVar(&options.AuthenticateRateLimiterDuration, "authenticate-rate-limiter-duration", s.AuthenticateRateLimiterDuration, "") fs.DurationVar(&options.AuthenticateRateLimiterDuration, "authenticate-rate-limiter-duration", s.AuthenticateRateLimiterDuration, "")
fs.IntVar(&options.MaxAuthenticateRetries, "authenticate-max-retries", s.MaxAuthenticateRetries, "") fs.IntVar(&options.MaxAuthenticateRetries, "authenticate-max-retries", s.MaxAuthenticateRetries, "")
fs.DurationVar(&options.TokenExpiration, "token-expiration", s.TokenExpiration, "Token expire duration, for example 30m/2h/1d, 0 means token never expire unless server restart.")
fs.BoolVar(&options.MultipleLogin, "multiple-login", s.MultipleLogin, "Allow multiple login with the same account, disable means only one user can login at the same time.") fs.BoolVar(&options.MultipleLogin, "multiple-login", s.MultipleLogin, "Allow multiple login with the same account, disable means only one user can login at the same time.")
fs.StringVar(&options.JwtSecret, "jwt-secret", s.JwtSecret, "Secret to sign jwt token, must not be empty.") fs.StringVar(&options.JwtSecret, "jwt-secret", s.JwtSecret, "Secret to sign jwt token, must not be empty.")
} }
...@@ -21,10 +21,10 @@ package token ...@@ -21,10 +21,10 @@ package token
// Issuer issues token to user, tokens are required to perform mutating requests to resources // Issuer issues token to user, tokens are required to perform mutating requests to resources
type Issuer interface { type Issuer interface {
// IssueTo issues a token a User, return error if issuing process failed // IssueTo issues a token a User, return error if issuing process failed
IssueTo(User) (string, *Claims, error) IssueTo(user User, expiresInSecond int) (string, error)
// Verify verifies a token, and return a User if it's a valid token, otherwise return error // Verify verifies a token, and return a User if it's a valid token, otherwise return error
Verify(string) (User, *Claims, error) Verify(string) (User, error)
// Revoke a token, // Revoke a token,
Revoke(token string) error Revoke(token string) error
......
...@@ -21,8 +21,8 @@ package token ...@@ -21,8 +21,8 @@ package token
import ( import (
"fmt" "fmt"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"kubesphere.io/kubesphere/pkg/api/auth"
"kubesphere.io/kubesphere/pkg/api/iam" "kubesphere.io/kubesphere/pkg/api/iam"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/server/errors" "kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/simple/client/cache" "kubesphere.io/kubesphere/pkg/simple/client/cache"
"time" "time"
...@@ -44,35 +44,35 @@ type Claims struct { ...@@ -44,35 +44,35 @@ type Claims struct {
type jwtTokenIssuer struct { type jwtTokenIssuer struct {
name string name string
options *auth.AuthenticationOptions options *authoptions.AuthenticationOptions
cache cache.Interface cache cache.Interface
keyFunc jwt.Keyfunc keyFunc jwt.Keyfunc
} }
func (s *jwtTokenIssuer) Verify(tokenString string) (User, *Claims, error) { func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
if len(tokenString) == 0 { if len(tokenString) == 0 {
return nil, nil, errInvalidToken return nil, errInvalidToken
} }
_, err := s.cache.Get(tokenCacheKey(tokenString)) _, err := s.cache.Get(tokenCacheKey(tokenString))
if err != nil { if err != nil {
if err == cache.ErrNoSuchKey { if err == cache.ErrNoSuchKey {
return nil, nil, errTokenExpired return nil, errTokenExpired
} }
return nil, nil, err return nil, err
} }
clm := &Claims{} clm := &Claims{}
_, err = jwt.ParseWithClaims(tokenString, clm, s.keyFunc) _, err = jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
return &iam.User{Name: clm.Username, UID: clm.UID}, clm, nil return &iam.User{Name: clm.Username, UID: clm.UID}, nil
} }
func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) { func (s *jwtTokenIssuer) IssueTo(user User, expiresInSecond int) (string, error) {
clm := &Claims{ clm := &Claims{
Username: user.GetName(), Username: user.GetName(),
UID: user.GetUID(), UID: user.GetUID(),
...@@ -83,8 +83,8 @@ func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) { ...@@ -83,8 +83,8 @@ func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) {
}, },
} }
if s.options.TokenExpiration > 0 { if expiresInSecond > 0 {
clm.ExpiresAt = clm.IssuedAt + int64(s.options.TokenExpiration.Seconds()) clm.ExpiresAt = clm.IssuedAt + int64(expiresInSecond)
} }
token := jwt.NewWithClaims(jwt.SigningMethodHS256, clm) token := jwt.NewWithClaims(jwt.SigningMethodHS256, clm)
...@@ -92,19 +92,19 @@ func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) { ...@@ -92,19 +92,19 @@ func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) {
tokenString, err := token.SignedString([]byte(s.options.JwtSecret)) tokenString, err := token.SignedString([]byte(s.options.JwtSecret))
if err != nil { if err != nil {
return "", nil, err return "", err
} }
s.cache.Set(tokenCacheKey(tokenString), tokenString, s.options.TokenExpiration) s.cache.Set(tokenCacheKey(tokenString), tokenString, time.Second*time.Duration(expiresInSecond))
return tokenString, clm, nil return tokenString, nil
} }
func (s *jwtTokenIssuer) Revoke(token string) error { func (s *jwtTokenIssuer) Revoke(token string) error {
return s.cache.Del(tokenCacheKey(token)) return s.cache.Del(tokenCacheKey(token))
} }
func NewJwtTokenIssuer(issuerName string, options *auth.AuthenticationOptions, cache cache.Interface) Issuer { func NewJwtTokenIssuer(issuerName string, options *authoptions.AuthenticationOptions, cache cache.Interface) Issuer {
return &jwtTokenIssuer{ return &jwtTokenIssuer{
name: issuerName, name: issuerName,
options: options, options: options,
......
...@@ -20,14 +20,14 @@ package token ...@@ -20,14 +20,14 @@ package token
import ( import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"kubesphere.io/kubesphere/pkg/api/auth"
"kubesphere.io/kubesphere/pkg/api/iam" "kubesphere.io/kubesphere/pkg/api/iam"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/simple/client/cache" "kubesphere.io/kubesphere/pkg/simple/client/cache"
"testing" "testing"
) )
func TestJwtTokenIssuer(t *testing.T) { func TestJwtTokenIssuer(t *testing.T) {
options := auth.NewAuthenticateOptions() options := authoptions.NewAuthenticateOptions()
options.JwtSecret = "kubesphere" options.JwtSecret = "kubesphere"
issuer := NewJwtTokenIssuer(DefaultIssuerName, options, cache.NewSimpleCache()) issuer := NewJwtTokenIssuer(DefaultIssuerName, options, cache.NewSimpleCache())
...@@ -54,12 +54,12 @@ func TestJwtTokenIssuer(t *testing.T) { ...@@ -54,12 +54,12 @@ func TestJwtTokenIssuer(t *testing.T) {
} }
t.Run(testCase.description, func(t *testing.T) { t.Run(testCase.description, func(t *testing.T) {
token, _, err := issuer.IssueTo(user) token, err := issuer.IssueTo(user, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
got, _, err := issuer.Verify(token) got, err := issuer.Verify(token)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
...@@ -5,13 +5,12 @@ import ( ...@@ -5,13 +5,12 @@ import (
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
"github.com/spf13/viper" "github.com/spf13/viper"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"kubesphere.io/kubesphere/pkg/api/auth" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/simple/client/alerting" "kubesphere.io/kubesphere/pkg/simple/client/alerting"
"kubesphere.io/kubesphere/pkg/simple/client/cache" "kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins" "kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/k8s" "kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/kubesphere"
"kubesphere.io/kubesphere/pkg/simple/client/ldap" "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/simple/client/logging/elasticsearch" "kubesphere.io/kubesphere/pkg/simple/client/logging/elasticsearch"
"kubesphere.io/kubesphere/pkg/simple/client/monitoring/prometheus" "kubesphere.io/kubesphere/pkg/simple/client/monitoring/prometheus"
...@@ -63,23 +62,18 @@ const ( ...@@ -63,23 +62,18 @@ const (
// Config defines everything needed for apiserver to deal with external services // Config defines everything needed for apiserver to deal with external services
type Config struct { type Config struct {
MySQLOptions *mysql.Options `json:"mysql,omitempty" yaml:"mysql,omitempty" mapstructure:"mysql"` MySQLOptions *mysql.Options `json:"mysql,omitempty" yaml:"mysql,omitempty" mapstructure:"mysql"`
DevopsOptions *jenkins.Options `json:"devops,omitempty" yaml:"devops,omitempty" mapstructure:"devops"` DevopsOptions *jenkins.Options `json:"devops,omitempty" yaml:"devops,omitempty" mapstructure:"devops"`
SonarQubeOptions *sonarqube.Options `json:"sonarqube,omitempty" yaml:"sonarQube,omitempty" mapstructure:"sonarqube"` SonarQubeOptions *sonarqube.Options `json:"sonarqube,omitempty" yaml:"sonarQube,omitempty" mapstructure:"sonarqube"`
KubernetesOptions *k8s.KubernetesOptions `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty" mapstructure:"kubernetes"` KubernetesOptions *k8s.KubernetesOptions `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty" mapstructure:"kubernetes"`
ServiceMeshOptions *servicemesh.Options `json:"servicemesh,omitempty" yaml:"servicemesh,omitempty" mapstructure:"servicemesh"` ServiceMeshOptions *servicemesh.Options `json:"servicemesh,omitempty" yaml:"servicemesh,omitempty" mapstructure:"servicemesh"`
LdapOptions *ldap.Options `json:"ldap,omitempty" yaml:"ldap,omitempty" mapstructure:"ldap"` LdapOptions *ldap.Options `json:"ldap,omitempty" yaml:"ldap,omitempty" mapstructure:"ldap"`
RedisOptions *cache.Options `json:"redis,omitempty" yaml:"redis,omitempty" mapstructure:"redis"` RedisOptions *cache.Options `json:"redis,omitempty" yaml:"redis,omitempty" mapstructure:"redis"`
S3Options *s3.Options `json:"s3,omitempty" yaml:"s3,omitempty" mapstructure:"s3"` S3Options *s3.Options `json:"s3,omitempty" yaml:"s3,omitempty" mapstructure:"s3"`
OpenPitrixOptions *openpitrix.Options `json:"openpitrix,omitempty" yaml:"openpitrix,omitempty" mapstructure:"openpitrix"` OpenPitrixOptions *openpitrix.Options `json:"openpitrix,omitempty" yaml:"openpitrix,omitempty" mapstructure:"openpitrix"`
MonitoringOptions *prometheus.Options `json:"monitoring,omitempty" yaml:"monitoring,omitempty" mapstructure:"monitoring"` MonitoringOptions *prometheus.Options `json:"monitoring,omitempty" yaml:"monitoring,omitempty" mapstructure:"monitoring"`
LoggingOptions *elasticsearch.Options `json:"logging,omitempty" yaml:"logging,omitempty" mapstructure:"logging"` LoggingOptions *elasticsearch.Options `json:"logging,omitempty" yaml:"logging,omitempty" mapstructure:"logging"`
AuthenticationOptions *authoptions.AuthenticationOptions `json:"authentication,omitempty" yaml:"authentication,omitempty" mapstructure:"authentication"`
// Options below are only loaded from configuration file, no command line flags for these options now.
KubeSphereOptions *kubesphere.Options `json:"-" yaml:"kubesphere,omitempty" mapstructure:"kubesphere"`
AuthenticateOptions *auth.AuthenticationOptions `json:"authentication,omitempty" yaml:"authenticate,omitempty" mapstructure:"authenticate"`
// Options used for enabling components, not actually used now. Once we switch Alerting/Notification API to kubesphere, // Options used for enabling components, not actually used now. Once we switch Alerting/Notification API to kubesphere,
// we can add these options to kubesphere command lines // we can add these options to kubesphere command lines
AlertingOptions *alerting.Options `json:"alerting,omitempty" yaml:"alerting,omitempty" mapstructure:"alerting"` AlertingOptions *alerting.Options `json:"alerting,omitempty" yaml:"alerting,omitempty" mapstructure:"alerting"`
...@@ -89,21 +83,20 @@ type Config struct { ...@@ -89,21 +83,20 @@ type Config struct {
// newConfig creates a default non-empty Config // newConfig creates a default non-empty Config
func New() *Config { func New() *Config {
return &Config{ return &Config{
MySQLOptions: mysql.NewMySQLOptions(), MySQLOptions: mysql.NewMySQLOptions(),
DevopsOptions: jenkins.NewDevopsOptions(), DevopsOptions: jenkins.NewDevopsOptions(),
SonarQubeOptions: sonarqube.NewSonarQubeOptions(), SonarQubeOptions: sonarqube.NewSonarQubeOptions(),
KubernetesOptions: k8s.NewKubernetesOptions(), KubernetesOptions: k8s.NewKubernetesOptions(),
ServiceMeshOptions: servicemesh.NewServiceMeshOptions(), ServiceMeshOptions: servicemesh.NewServiceMeshOptions(),
LdapOptions: ldap.NewOptions(), LdapOptions: ldap.NewOptions(),
RedisOptions: cache.NewRedisOptions(), RedisOptions: cache.NewRedisOptions(),
S3Options: s3.NewS3Options(), S3Options: s3.NewS3Options(),
OpenPitrixOptions: openpitrix.NewOptions(), OpenPitrixOptions: openpitrix.NewOptions(),
MonitoringOptions: prometheus.NewPrometheusOptions(), MonitoringOptions: prometheus.NewPrometheusOptions(),
KubeSphereOptions: kubesphere.NewKubeSphereOptions(), AlertingOptions: alerting.NewAlertingOptions(),
AlertingOptions: alerting.NewAlertingOptions(), NotificationOptions: notification.NewNotificationOptions(),
NotificationOptions: notification.NewNotificationOptions(), LoggingOptions: elasticsearch.NewElasticSearchOptions(),
LoggingOptions: elasticsearch.NewElasticSearchOptions(), AuthenticationOptions: authoptions.NewAuthenticateOptions(),
AuthenticateOptions: auth.NewAuthenticateOptions(),
} }
} }
...@@ -125,17 +118,9 @@ func TryLoadFromDisk() (*Config, error) { ...@@ -125,17 +118,9 @@ func TryLoadFromDisk() (*Config, error) {
} }
conf := New() conf := New()
if err := viper.Unmarshal(conf); err != nil { if err := viper.Unmarshal(conf); err != nil {
return nil, err return nil, err
} else {
// make sure kubesphere options always exists
if conf.KubeSphereOptions == nil {
conf.KubeSphereOptions = kubesphere.NewKubeSphereOptions()
} else {
ksOptions := kubesphere.NewKubeSphereOptions()
conf.KubeSphereOptions.ApplyTo(ksOptions)
conf.KubeSphereOptions = ksOptions
}
} }
return conf, nil return conf, nil
...@@ -151,7 +136,7 @@ func (conf *Config) InstallAPI(c *restful.Container) { ...@@ -151,7 +136,7 @@ func (conf *Config) InstallAPI(c *restful.Container) {
ws.Route(ws.GET("/configz"). ws.Route(ws.GET("/configz").
To(func(request *restful.Request, response *restful.Response) { To(func(request *restful.Request, response *restful.Response) {
conf.stripEmptyOptions() conf.stripEmptyOptions()
response.WriteAsJson(conf.toMap()) response.WriteAsJson(conf.ToMap())
}). }).
Doc("Get system components configuration"). Doc("Get system components configuration").
Produces(restful.MIME_JSON). Produces(restful.MIME_JSON).
...@@ -163,7 +148,8 @@ func (conf *Config) InstallAPI(c *restful.Container) { ...@@ -163,7 +148,8 @@ func (conf *Config) InstallAPI(c *restful.Container) {
// convertToMap simply converts config to map[string]bool // convertToMap simply converts config to map[string]bool
// to hide sensitive information // to hide sensitive information
func (conf *Config) toMap() map[string]bool { func (conf *Config) ToMap() map[string]bool {
conf.stripEmptyOptions()
result := make(map[string]bool, 0) result := make(map[string]bool, 0)
if conf == nil { if conf == nil {
......
...@@ -2,14 +2,15 @@ package config ...@@ -2,14 +2,15 @@ package config
import ( import (
"fmt" "fmt"
"github.com/google/go-cmp/cmp"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"io/ioutil" "io/ioutil"
"kubesphere.io/kubesphere/pkg/api/auth" "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/simple/client/alerting" "kubesphere.io/kubesphere/pkg/simple/client/alerting"
"kubesphere.io/kubesphere/pkg/simple/client/cache" "kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins" "kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/k8s" "kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/kubesphere"
"kubesphere.io/kubesphere/pkg/simple/client/ldap" "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/simple/client/logging/elasticsearch" "kubesphere.io/kubesphere/pkg/simple/client/logging/elasticsearch"
"kubesphere.io/kubesphere/pkg/simple/client/monitoring/prometheus" "kubesphere.io/kubesphere/pkg/simple/client/monitoring/prometheus"
...@@ -26,7 +27,7 @@ import ( ...@@ -26,7 +27,7 @@ import (
) )
func newTestConfig() *Config { func newTestConfig() *Config {
conf := &Config{ var conf = &Config{
MySQLOptions: &mysql.Options{ MySQLOptions: &mysql.Options{
Host: "10.68.96.5:3306", Host: "10.68.96.5:3306",
Username: "root", Username: "root",
...@@ -96,22 +97,46 @@ func newTestConfig() *Config { ...@@ -96,22 +97,46 @@ func newTestConfig() *Config {
IndexPrefix: "elk", IndexPrefix: "elk",
Version: "6", Version: "6",
}, },
KubeSphereOptions: &kubesphere.Options{
APIServer: "http://ks-apiserver.kubesphere-system.svc",
AccountServer: "http://ks-account.kubesphere-system.svc",
},
AlertingOptions: &alerting.Options{ AlertingOptions: &alerting.Options{
Endpoint: "http://alerting.kubesphere-alerting-system.svc:9200", Endpoint: "http://alerting.kubesphere-alerting-system.svc:9200",
}, },
NotificationOptions: &notification.Options{ NotificationOptions: &notification.Options{
Endpoint: "http://notification.kubesphere-alerting-system.svc:9200", Endpoint: "http://notification.kubesphere-alerting-system.svc:9200",
}, },
AuthenticateOptions: &auth.AuthenticationOptions{ AuthenticationOptions: &authoptions.AuthenticationOptions{
AuthenticateRateLimiterMaxTries: 5, AuthenticateRateLimiterMaxTries: 5,
AuthenticateRateLimiterDuration: 30 * time.Minute, AuthenticateRateLimiterDuration: 30 * time.Minute,
MaxAuthenticateRetries: 6, MaxAuthenticateRetries: 6,
TokenExpiration: 30 * time.Minute, JwtSecret: "xxxxxx",
MultipleLogin: false, MultipleLogin: false,
OAuthOptions: &oauth.Options{
IdentityProviders: []oauth.IdentityProvider{{
Name: "github",
MappingMethod: "auto",
LoginRedirect: true,
Type: oauth.IdentityProviderTypeGithub,
Github: &oauth.Github{
ClientID: "de6ff7bed0304e487b6e",
ClientSecret: "xxxxxx-xxxxx-xxxxx",
Endpoint: oauth.Endpoint{
AuthURL: "https://github.com/login/oauth/authorize",
TokenURL: "https://github.com/login/oauth/token",
},
RedirectURL: "https://ks-console.kubesphere-system.svc/oauth/callbak/github",
Scopes: []string{"user"},
},
}},
Clients: []oauth.Client{{
Name: "kubesphere-console-client",
Secret: "xxxxxx-xxxxxx-xxxxxx",
RespondWithChallenges: true,
RedirectURIs: []string{"http://ks-console.kubesphere-system.svc/oauth/token/implicit"},
GrantMethod: oauth.GrantHandlerAuto,
AccessTokenInactivityTimeoutSeconds: nil,
}},
AccessTokenMaxAgeSeconds: 86400,
AccessTokenInactivityTimeoutSeconds: 0,
},
}, },
} }
return conf return conf
...@@ -153,47 +178,8 @@ func TestGet(t *testing.T) { ...@@ -153,47 +178,8 @@ func TestGet(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
cmp.Diff(conf, conf2)
if diff := reflectutils.Equal(conf, conf2); diff != nil { if diff := reflectutils.Equal(conf, conf2); diff != nil {
t.Fatal(diff) t.Fatal(diff)
} }
} }
func TestKubeSphereOptions(t *testing.T) {
conf := newTestConfig()
t.Run("save nil kubesphere options", func(t *testing.T) {
savedConf := *conf
savedConf.KubeSphereOptions = nil
saveTestConfig(t, &savedConf)
defer cleanTestConfig(t)
loadedConf, err := TryLoadFromDisk()
if err != nil {
t.Fatal(err)
}
if diff := reflectutils.Equal(conf, loadedConf); diff != nil {
t.Fatal(diff)
}
})
t.Run("save partially kubesphere options", func(t *testing.T) {
savedConf := *conf
savedConf.KubeSphereOptions.APIServer = "http://example.com"
savedConf.KubeSphereOptions.AccountServer = ""
saveTestConfig(t, &savedConf)
defer cleanTestConfig(t)
loadedConf, err := TryLoadFromDisk()
if err != nil {
t.Fatal(err)
}
savedConf.KubeSphereOptions.AccountServer = "http://ks-account.kubesphere-system.svc"
if diff := reflectutils.Equal(&savedConf, loadedConf); diff != nil {
t.Fatal(diff)
}
})
}
...@@ -20,30 +20,17 @@ package workspace ...@@ -20,30 +20,17 @@ package workspace
import ( import (
"context" "context"
"fmt"
corev1 "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
log "k8s.io/klog"
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/simple/client/kubesphere"
"kubesphere.io/kubesphere/pkg/utils/sliceutil" "kubesphere.io/kubesphere/pkg/utils/sliceutil"
"reflect"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source" "sigs.k8s.io/controller-runtime/pkg/source"
"sync"
) )
const ( const (
...@@ -54,14 +41,14 @@ const ( ...@@ -54,14 +41,14 @@ const (
// Add creates a new Workspace Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller // Add creates a new Workspace Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
// and Start it when the Manager is Started. // and Start it when the Manager is Started.
func Add(mgr manager.Manager, kubesphereClient kubesphere.Interface) error { func Add(mgr manager.Manager) error {
return add(mgr, newReconciler(mgr, kubesphereClient)) return add(mgr, newReconciler(mgr))
} }
// newReconciler returns a new reconcile.Reconciler // newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager, kubesphereClient kubesphere.Interface) reconcile.Reconciler { func newReconciler(mgr manager.Manager) reconcile.Reconciler {
return &ReconcileWorkspace{Client: mgr.GetClient(), scheme: mgr.GetScheme(), return &ReconcileWorkspace{Client: mgr.GetClient(), scheme: mgr.GetScheme(),
recorder: mgr.GetEventRecorderFor("workspace-controller"), ksclient: kubesphereClient} recorder: mgr.GetEventRecorderFor("workspace-controller")}
} }
// add adds a new Controller to mgr with r as the reconcile.Reconciler // add adds a new Controller to mgr with r as the reconcile.Reconciler
...@@ -88,7 +75,6 @@ type ReconcileWorkspace struct { ...@@ -88,7 +75,6 @@ type ReconcileWorkspace struct {
client.Client client.Client
scheme *runtime.Scheme scheme *runtime.Scheme
recorder record.EventRecorder recorder record.EventRecorder
ksclient kubesphere.Interface
} }
// Reconcile reads that state of the cluster for a Workspace object and makes changes based on the state read // Reconcile reads that state of the cluster for a Workspace object and makes changes based on the state read
...@@ -125,9 +111,6 @@ func (r *ReconcileWorkspace) Reconcile(request reconcile.Request) (reconcile.Res ...@@ -125,9 +111,6 @@ func (r *ReconcileWorkspace) Reconcile(request reconcile.Request) (reconcile.Res
// The object is being deleted // The object is being deleted
if sliceutil.HasString(instance.ObjectMeta.Finalizers, finalizer) { if sliceutil.HasString(instance.ObjectMeta.Finalizers, finalizer) {
// our finalizer is present, so lets handle our external dependency // our finalizer is present, so lets handle our external dependency
if err := r.deleteDevOpsProjects(instance); err != nil {
return reconcile.Result{}, err
}
// remove our finalizer from the list and update it. // remove our finalizer from the list and update it.
instance.ObjectMeta.Finalizers = sliceutil.RemoveString(instance.ObjectMeta.Finalizers, func(item string) bool { instance.ObjectMeta.Finalizers = sliceutil.RemoveString(instance.ObjectMeta.Finalizers, func(item string) bool {
...@@ -142,497 +125,5 @@ func (r *ReconcileWorkspace) Reconcile(request reconcile.Request) (reconcile.Res ...@@ -142,497 +125,5 @@ func (r *ReconcileWorkspace) Reconcile(request reconcile.Request) (reconcile.Res
return reconcile.Result{}, nil return reconcile.Result{}, nil
} }
if err = r.createWorkspaceAdmin(instance); err != nil {
return reconcile.Result{}, err
}
if err = r.createWorkspaceRegular(instance); err != nil {
return reconcile.Result{}, err
}
if err = r.createWorkspaceViewer(instance); err != nil {
return reconcile.Result{}, err
}
if err = r.createWorkspaceRoleBindings(instance); err != nil {
return reconcile.Result{}, err
}
if err = r.bindNamespaces(instance); err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{}, nil return reconcile.Result{}, nil
} }
func (r *ReconcileWorkspace) createWorkspaceAdmin(instance *tenantv1alpha1.Workspace) error {
found := &rbac.ClusterRole{}
admin := getWorkspaceAdmin(instance.Name)
if err := controllerutil.SetControllerReference(instance, admin, r.scheme); err != nil {
return err
}
err := r.Get(context.TODO(), types.NamespacedName{Name: admin.Name}, found)
if err != nil && errors.IsNotFound(err) {
log.Info("Creating workspace role", "workspace", instance.Name, "name", admin.Name)
err = r.Create(context.TODO(), admin)
if err != nil {
return err
}
found = admin
} else if err != nil {
// Error reading the object - requeue the request.
return err
}
// Update the found object and write the result back if there are any changes
if !reflect.DeepEqual(admin.Rules, found.Rules) || !reflect.DeepEqual(admin.Labels, found.Labels) || !reflect.DeepEqual(admin.Annotations, found.Annotations) {
found.Rules = admin.Rules
found.Labels = admin.Labels
found.Annotations = admin.Annotations
log.Info("Updating workspace role", "workspace", instance.Name, "name", admin.Name)
err = r.Update(context.TODO(), found)
if err != nil {
return err
}
}
return nil
}
func (r *ReconcileWorkspace) createWorkspaceRegular(instance *tenantv1alpha1.Workspace) error {
found := &rbac.ClusterRole{}
regular := getWorkspaceRegular(instance.Name)
if err := controllerutil.SetControllerReference(instance, regular, r.scheme); err != nil {
return err
}
err := r.Get(context.TODO(), types.NamespacedName{Name: regular.Name}, found)
if err != nil && errors.IsNotFound(err) {
log.Info("Creating workspace role", "workspace", instance.Name, "name", regular.Name)
err = r.Create(context.TODO(), regular)
// Error reading the object - requeue the request.
if err != nil {
return err
}
found = regular
} else if err != nil {
// Error reading the object - requeue the request.
return err
}
// Update the found object and write the result back if there are any changes
if !reflect.DeepEqual(regular.Rules, found.Rules) || !reflect.DeepEqual(regular.Labels, found.Labels) || !reflect.DeepEqual(regular.Annotations, found.Annotations) {
found.Rules = regular.Rules
found.Labels = regular.Labels
found.Annotations = regular.Annotations
log.Info("Updating workspace role", "workspace", instance.Name, "name", regular.Name)
err = r.Update(context.TODO(), found)
if err != nil {
return err
}
}
return nil
}
func (r *ReconcileWorkspace) createWorkspaceViewer(instance *tenantv1alpha1.Workspace) error {
found := &rbac.ClusterRole{}
viewer := getWorkspaceViewer(instance.Name)
if err := controllerutil.SetControllerReference(instance, viewer, r.scheme); err != nil {
return err
}
err := r.Get(context.TODO(), types.NamespacedName{Name: viewer.Name}, found)
if err != nil && errors.IsNotFound(err) {
log.Info("Creating workspace role", "workspace", instance.Name, "name", viewer.Name)
err = r.Create(context.TODO(), viewer)
// Error reading the object - requeue the request.
if err != nil {
return err
}
found = viewer
} else if err != nil {
// Error reading the object - requeue the request.
return err
}
// Update the found object and write the result back if there are any changes
if !reflect.DeepEqual(viewer.Rules, found.Rules) || !reflect.DeepEqual(viewer.Labels, found.Labels) || !reflect.DeepEqual(viewer.Annotations, found.Annotations) {
found.Rules = viewer.Rules
found.Labels = viewer.Labels
found.Annotations = viewer.Annotations
log.Info("Updating workspace role", "workspace", instance.Name, "name", viewer.Name)
err = r.Update(context.TODO(), found)
if err != nil {
return err
}
}
return nil
}
func (r *ReconcileWorkspace) createGroup(instance *tenantv1alpha1.Workspace) error {
_, err := r.ksclient.DescribeGroup(instance.Name)
group := &models.Group{
Name: instance.Name,
}
if err != nil && kubesphere.IsNotFound(err) {
log.Info("Creating group", "group name", instance.Name)
_, err = r.ksclient.CreateGroup(group)
if err != nil {
if kubesphere.IsExist(err) {
return nil
}
return err
}
} else if err != nil {
return err
}
return nil
}
func (r *ReconcileWorkspace) deleteGroup(instance *tenantv1alpha1.Workspace) error {
log.Info("Creating group", "group name", instance.Name)
if err := r.ksclient.DeleteGroup(instance.Name); err != nil {
if kubesphere.IsNotFound(err) {
return nil
}
return err
}
return nil
}
func (r *ReconcileWorkspace) deleteDevOpsProjects(instance *tenantv1alpha1.Workspace) error {
var wg sync.WaitGroup
log.Info("Delete DevOps Projects")
for {
projects, err := r.ksclient.ListWorkspaceDevOpsProjects(instance.Name)
if err != nil {
log.Error(err, "Failed to Get Workspace's DevOps Projects", "ws", instance.Name)
return err
}
if projects.TotalCount == 0 {
return nil
}
errChan := make(chan error, len(projects.Items))
for _, project := range projects.Items {
wg.Add(1)
go func(workspace, devops string) {
err = r.ksclient.DeleteWorkspaceDevOpsProjects(workspace, devops)
errChan <- err
wg.Done()
}(instance.Name, project.ProjectId)
}
wg.Wait()
close(errChan)
for err := range errChan {
if err != nil {
log.Error(err, "delete devops project error")
return err
}
}
}
}
func (r *ReconcileWorkspace) createWorkspaceRoleBindings(instance *tenantv1alpha1.Workspace) error {
adminRoleBinding := &rbac.ClusterRoleBinding{}
adminRoleBinding.Name = getWorkspaceAdminRoleBindingName(instance.Name)
adminRoleBinding.Labels = map[string]string{constants.WorkspaceLabelKey: instance.Name}
adminRoleBinding.RoleRef = rbac.RoleRef{APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", Name: getWorkspaceAdminRoleName(instance.Name)}
workspaceManager := rbac.Subject{APIGroup: "rbac.authorization.k8s.io", Kind: "User", Name: instance.Spec.Manager}
if workspaceManager.Name != "" {
adminRoleBinding.Subjects = []rbac.Subject{workspaceManager}
} else {
adminRoleBinding.Subjects = []rbac.Subject{}
}
if err := controllerutil.SetControllerReference(instance, adminRoleBinding, r.scheme); err != nil {
return err
}
foundRoleBinding := &rbac.ClusterRoleBinding{}
err := r.Get(context.TODO(), types.NamespacedName{Name: adminRoleBinding.Name}, foundRoleBinding)
if err != nil && errors.IsNotFound(err) {
log.Info("Creating workspace role binding", "workspace", instance.Name, "name", adminRoleBinding.Name)
err = r.Create(context.TODO(), adminRoleBinding)
// Error reading the object - requeue the request.
if err != nil {
return err
}
foundRoleBinding = adminRoleBinding
} else if err != nil {
// Error reading the object - requeue the request.
return err
}
// Update the found object and write the result back if there are any changes
if !reflect.DeepEqual(adminRoleBinding.RoleRef, foundRoleBinding.RoleRef) {
log.Info("Deleting conflict workspace role binding", "workspace", instance.Name, "name", adminRoleBinding.Name)
err = r.Delete(context.TODO(), foundRoleBinding)
if err != nil {
return err
}
return fmt.Errorf("conflict workspace role binding %s, waiting for recreate", foundRoleBinding.Name)
}
if workspaceManager.Name != "" && !hasSubject(foundRoleBinding.Subjects, workspaceManager) {
foundRoleBinding.Subjects = append(foundRoleBinding.Subjects, workspaceManager)
log.Info("Updating workspace role binding", "workspace", instance.Name, "name", adminRoleBinding.Name)
err = r.Update(context.TODO(), foundRoleBinding)
if err != nil {
return err
}
}
regularRoleBinding := &rbac.ClusterRoleBinding{}
regularRoleBinding.Name = getWorkspaceRegularRoleBindingName(instance.Name)
regularRoleBinding.Labels = map[string]string{constants.WorkspaceLabelKey: instance.Name}
regularRoleBinding.RoleRef = rbac.RoleRef{APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", Name: getWorkspaceRegularRoleName(instance.Name)}
regularRoleBinding.Subjects = []rbac.Subject{}
if err = controllerutil.SetControllerReference(instance, regularRoleBinding, r.scheme); err != nil {
return err
}
err = r.Get(context.TODO(), types.NamespacedName{Name: regularRoleBinding.Name}, foundRoleBinding)
if err != nil && errors.IsNotFound(err) {
log.Info("Creating workspace role binding", "workspace", instance.Name, "name", regularRoleBinding.Name)
err = r.Create(context.TODO(), regularRoleBinding)
// Error reading the object - requeue the request.
if err != nil {
return err
}
foundRoleBinding = regularRoleBinding
} else if err != nil {
// Error reading the object - requeue the request.
return err
}
// Update the found object and write the result back if there are any changes
if !reflect.DeepEqual(regularRoleBinding.RoleRef, foundRoleBinding.RoleRef) {
log.Info("Deleting conflict workspace role binding", "workspace", instance.Name, "name", regularRoleBinding.Name)
err = r.Delete(context.TODO(), foundRoleBinding)
if err != nil {
return err
}
return fmt.Errorf("conflict workspace role binding %s, waiting for recreate", foundRoleBinding.Name)
}
viewerRoleBinding := &rbac.ClusterRoleBinding{}
viewerRoleBinding.Name = getWorkspaceViewerRoleBindingName(instance.Name)
viewerRoleBinding.Labels = map[string]string{constants.WorkspaceLabelKey: instance.Name}
viewerRoleBinding.RoleRef = rbac.RoleRef{APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", Name: getWorkspaceViewerRoleName(instance.Name)}
viewerRoleBinding.Subjects = []rbac.Subject{}
if err = controllerutil.SetControllerReference(instance, viewerRoleBinding, r.scheme); err != nil {
return err
}
err = r.Get(context.TODO(), types.NamespacedName{Name: viewerRoleBinding.Name}, foundRoleBinding)
if err != nil && errors.IsNotFound(err) {
log.Info("Creating workspace role binding", "workspace", instance.Name, "name", viewerRoleBinding.Name)
err = r.Create(context.TODO(), viewerRoleBinding)
// Error reading the object - requeue the request.
if err != nil {
return err
}
foundRoleBinding = viewerRoleBinding
} else if err != nil {
// Error reading the object - requeue the request.
return err
}
// Update the found object and write the result back if there are any changes
if !reflect.DeepEqual(viewerRoleBinding.RoleRef, foundRoleBinding.RoleRef) {
log.Info("Deleting conflict workspace role binding", "workspace", instance.Name, "name", viewerRoleBinding.Name)
err = r.Delete(context.TODO(), foundRoleBinding)
if err != nil {
return err
}
return fmt.Errorf("conflict workspace role binding %s, waiting for recreate", foundRoleBinding.Name)
}
return nil
}
func (r *ReconcileWorkspace) bindNamespaces(instance *tenantv1alpha1.Workspace) error {
nsList := &corev1.NamespaceList{}
options := client.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{constants.WorkspaceLabelKey: instance.Name})}
err := r.List(context.TODO(), nsList, &options)
if err != nil {
log.Error(err, fmt.Sprintf("list workspace %s namespace failed", instance.Name))
return err
}
for _, namespace := range nsList.Items {
if !metav1.IsControlledBy(&namespace, instance) {
if err := controllerutil.SetControllerReference(instance, &namespace, r.scheme); err != nil {
return err
}
log.Info("Bind workspace", "namespace", namespace.Name, "workspace", instance.Name)
err = r.Update(context.TODO(), &namespace)
if err != nil {
return err
}
}
}
return nil
}
func hasSubject(subjects []rbac.Subject, user rbac.Subject) bool {
for _, subject := range subjects {
if reflect.DeepEqual(subject, user) {
return true
}
}
return false
}
func getWorkspaceAdminRoleName(workspaceName string) string {
return fmt.Sprintf("workspace:%s:admin", workspaceName)
}
func getWorkspaceRegularRoleName(workspaceName string) string {
return fmt.Sprintf("workspace:%s:regular", workspaceName)
}
func getWorkspaceViewerRoleName(workspaceName string) string {
return fmt.Sprintf("workspace:%s:viewer", workspaceName)
}
func getWorkspaceAdminRoleBindingName(workspaceName string) string {
return fmt.Sprintf("workspace:%s:admin", workspaceName)
}
func getWorkspaceRegularRoleBindingName(workspaceName string) string {
return fmt.Sprintf("workspace:%s:regular", workspaceName)
}
func getWorkspaceViewerRoleBindingName(workspaceName string) string {
return fmt.Sprintf("workspace:%s:viewer", workspaceName)
}
func getWorkspaceAdmin(workspaceName string) *rbac.ClusterRole {
admin := &rbac.ClusterRole{}
admin.Name = getWorkspaceAdminRoleName(workspaceName)
admin.Labels = map[string]string{constants.WorkspaceLabelKey: workspaceName}
admin.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceAdmin, constants.DescriptionAnnotationKey: workspaceAdminDescription, constants.CreatorAnnotationKey: constants.System}
admin.Rules = []rbac.PolicyRule{
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
ResourceNames: []string{workspaceName},
Resources: []string{"workspaces", "workspaces/*"},
},
{
Verbs: []string{"watch"},
APIGroups: []string{""},
Resources: []string{"namespaces"},
},
{
Verbs: []string{"list"},
APIGroups: []string{"iam.kubesphere.io"},
Resources: []string{"users"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"openpitrix.io"},
Resources: []string{"categories"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"openpitrix.io"},
Resources: []string{"applications", "apps", "apps/versions", "apps/events", "apps/action", "apps/audits", "repos", "repos/action", "attachments"},
},
}
return admin
}
func getWorkspaceRegular(workspaceName string) *rbac.ClusterRole {
regular := &rbac.ClusterRole{}
regular.Name = getWorkspaceRegularRoleName(workspaceName)
regular.Labels = map[string]string{constants.WorkspaceLabelKey: workspaceName}
regular.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceRegular, constants.DescriptionAnnotationKey: workspaceRegularDescription, constants.CreatorAnnotationKey: constants.System}
regular.Rules = []rbac.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"*"},
Resources: []string{"workspaces"},
ResourceNames: []string{workspaceName},
}, {
Verbs: []string{"create"},
APIGroups: []string{"tenant.kubesphere.io"},
Resources: []string{"workspaces/namespaces", "workspaces/devops"},
ResourceNames: []string{workspaceName},
},
{
Verbs: []string{"get"},
APIGroups: []string{"iam.kubesphere.io"},
ResourceNames: []string{workspaceName},
Resources: []string{"workspaces/members"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"openpitrix.io"},
Resources: []string{"apps/events", "apps/action", "apps/audits", "categories"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"openpitrix.io"},
Resources: []string{"applications", "apps", "apps/versions", "repos", "repos/action", "attachments"},
},
}
return regular
}
func getWorkspaceViewer(workspaceName string) *rbac.ClusterRole {
viewer := &rbac.ClusterRole{}
viewer.Name = getWorkspaceViewerRoleName(workspaceName)
viewer.Labels = map[string]string{constants.WorkspaceLabelKey: workspaceName}
viewer.Annotations = map[string]string{constants.DisplayNameAnnotationKey: constants.WorkspaceViewer, constants.DescriptionAnnotationKey: workspaceViewerDescription, constants.CreatorAnnotationKey: constants.System}
viewer.Rules = []rbac.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"*"},
ResourceNames: []string{workspaceName},
Resources: []string{"workspaces", "workspaces/*"},
},
{
Verbs: []string{"watch"},
APIGroups: []string{""},
Resources: []string{"namespaces"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"openpitrix.io"},
Resources: []string{"applications", "apps", "apps/events", "apps/action", "apps/audits", "apps/versions", "repos", "categories", "attachments"},
},
}
return viewer
}
...@@ -2,7 +2,7 @@ package v1alpha2 ...@@ -2,7 +2,7 @@ package v1alpha2
import ( import (
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
"kubesphere.io/kubesphere/pkg/api/auth" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/iam/im" "kubesphere.io/kubesphere/pkg/models/iam/im"
...@@ -16,7 +16,7 @@ type iamHandler struct { ...@@ -16,7 +16,7 @@ type iamHandler struct {
imOperator im.IdentityManagementInterface imOperator im.IdentityManagementInterface
} }
func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) *iamHandler { func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *authoptions.AuthenticationOptions) *iamHandler {
return &iamHandler{ return &iamHandler{
amOperator: am.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()), amOperator: am.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
imOperator: im.NewLDAPOperator(ldapClient), imOperator: im.NewLDAPOperator(ldapClient),
......
...@@ -22,7 +22,7 @@ import ( ...@@ -22,7 +22,7 @@ import (
"github.com/emicklei/go-restful-openapi" "github.com/emicklei/go-restful-openapi"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/auth" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/informers"
...@@ -38,7 +38,7 @@ const groupName = "iam.kubesphere.io" ...@@ -38,7 +38,7 @@ const groupName = "iam.kubesphere.io"
var GroupVersion = schema.GroupVersion{Group: groupName, Version: "v1alpha2"} var GroupVersion = schema.GroupVersion{Group: groupName, Version: "v1alpha2"}
func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) error { func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *authoptions.AuthenticationOptions) error {
ws := runtime.NewWebService(GroupVersion) ws := runtime.NewWebService(GroupVersion)
handler := newIAMHandler(k8sClient, factory, ldapClient, cacheClient, options) handler := newIAMHandler(k8sClient, factory, ldapClient, cacheClient, options)
......
...@@ -26,18 +26,19 @@ import ( ...@@ -26,18 +26,19 @@ import (
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/auth" "kubesphere.io/kubesphere/pkg/api/auth"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token" "kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
"kubesphere.io/kubesphere/pkg/apiserver/request" "kubesphere.io/kubesphere/pkg/apiserver/request"
"net/http" "net/http"
) )
type oauthHandler struct { type oauthHandler struct {
issuer token.Issuer issuer token.Issuer
config oauth.Configuration options *authoptions.AuthenticationOptions
} }
func newOAUTHHandler(issuer token.Issuer, config oauth.Configuration) *oauthHandler { func newOAUTHHandler(issuer token.Issuer, options *authoptions.AuthenticationOptions) *oauthHandler {
return &oauthHandler{issuer: issuer, config: config} return &oauthHandler{issuer: issuer, options: options}
} }
// Implement webhook authentication interface // Implement webhook authentication interface
...@@ -59,7 +60,7 @@ func (h *oauthHandler) TokenReviewHandler(req *restful.Request, resp *restful.Re ...@@ -59,7 +60,7 @@ func (h *oauthHandler) TokenReviewHandler(req *restful.Request, resp *restful.Re
return return
} }
user, _, err := h.issuer.Verify(tokenReview.Spec.Token) user, err := h.issuer.Verify(tokenReview.Spec.Token)
if err != nil { if err != nil {
klog.Errorln(err) klog.Errorln(err)
...@@ -82,8 +83,9 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp ...@@ -82,8 +83,9 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp
user, ok := request.UserFrom(req.Request.Context()) user, ok := request.UserFrom(req.Request.Context())
clientId := req.QueryParameter("client_id") clientId := req.QueryParameter("client_id")
responseType := req.QueryParameter("response_type") responseType := req.QueryParameter("response_type")
redirectURI := req.QueryParameter("redirect_uri")
conf, err := h.config.Load(clientId) conf, err := h.options.OAuthOptions.GetOAuthClient(clientId)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
...@@ -103,7 +105,21 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp ...@@ -103,7 +105,21 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp
return return
} }
accessToken, clm, err := h.issuer.IssueTo(user) expiresIn := h.options.OAuthOptions.AccessTokenMaxAgeSeconds
if conf.AccessTokenMaxAgeSeconds != nil {
expiresIn = *conf.AccessTokenMaxAgeSeconds
}
accessToken, err := h.issuer.IssueTo(user, expiresIn)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
return
}
redirectURL, err := conf.ResolveRedirectURL(redirectURI)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
...@@ -111,11 +127,63 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp ...@@ -111,11 +127,63 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp
return return
} }
redirectURL := fmt.Sprintf("%s?access_token=%s&token_type=Bearer", conf.RedirectURL, accessToken) redirectURL = fmt.Sprintf("%s#access_token=%s&token_type=Bearer", redirectURL, accessToken)
expiresIn := clm.ExpiresAt - clm.IssuedAt
if expiresIn > 0 { if expiresIn > 0 {
redirectURL = fmt.Sprintf("%s&expires_in=%v", redirectURL, expiresIn) redirectURL = fmt.Sprintf("%s&expires_in=%v", redirectURL, expiresIn)
} }
resp.Header().Set("Content-Type", "text/plain")
http.Redirect(resp, req.Request, redirectURL, http.StatusFound) http.Redirect(resp, req.Request, redirectURL, http.StatusFound)
} }
func (h *oauthHandler) OAuthCallBackHandler(req *restful.Request, resp *restful.Response) {
code := req.QueryParameter("code")
name := req.PathParameter("callback")
if code == "" {
err := apierrors.NewUnauthorized("Unauthorized: missing code")
resp.WriteError(http.StatusUnauthorized, err)
}
idP, err := h.options.OAuthOptions.GetIdentityProvider(name)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
}
oauthIdentityProvider, err := idP.GetOAuth2IdentityProviderInstance()
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
}
user, err := oauthIdentityProvider.IdentityExchange(code)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
}
expiresIn := h.options.OAuthOptions.AccessTokenMaxAgeSeconds
accessToken, err := h.issuer.IssueTo(user, expiresIn)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
return
}
result := oauth.Token{
AccessToken: accessToken,
TokenType: "Bearer",
ExpiresIn: expiresIn,
}
resp.WriteEntity(result)
return
}
...@@ -23,19 +23,19 @@ import ( ...@@ -23,19 +23,19 @@ import (
restfulspec "github.com/emicklei/go-restful-openapi" restfulspec "github.com/emicklei/go-restful-openapi"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/auth" "kubesphere.io/kubesphere/pkg/api/auth"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token" "kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
"kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/constants"
"net/http" "net/http"
) )
func AddToContainer(c *restful.Container, issuer token.Issuer, configuration oauth.Configuration) error { func AddToContainer(c *restful.Container, issuer token.Issuer, options *authoptions.AuthenticationOptions) error {
ws := &restful.WebService{} ws := &restful.WebService{}
ws.Path("/oauth"). ws.Path("/oauth").
Consumes(restful.MIME_JSON). Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON) Produces(restful.MIME_JSON)
handler := newOAUTHHandler(issuer, configuration) handler := newOAUTHHandler(issuer, options)
// Implement webhook authentication interface // Implement webhook authentication interface
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
...@@ -46,16 +46,14 @@ func AddToContainer(c *restful.Container, issuer token.Issuer, configuration oau ...@@ -46,16 +46,14 @@ func AddToContainer(c *restful.Container, issuer token.Issuer, configuration oau
Returns(http.StatusOK, api.StatusOK, auth.TokenReview{}). Returns(http.StatusOK, api.StatusOK, auth.TokenReview{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag})) Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
// TODO Built-in oauth2 server (provider) // Only support implicit grant flow
// web console use 'Resource Owner Password Credentials Grant' or 'Client Credentials Grant' request for an OAuth token // https://tools.ietf.org/html/rfc6749#section-4.2
// https://tools.ietf.org/html/rfc6749#section-4.3
// https://tools.ietf.org/html/rfc6749#section-4.4
// curl -u admin:P@88w0rd 'http://ks-apiserver.kubesphere-system.svc/oauth/authorize?client_id=kubesphere-console-client&response_type=token' -v // curl -u admin:P@88w0rd 'http://ks-apiserver.kubesphere-system.svc/oauth/authorize?client_id=kubesphere-console-client&response_type=token' -v
ws.Route(ws.GET("/authorize"). ws.Route(ws.GET("/authorize").
To(handler.AuthorizeHandler)) To(handler.AuthorizeHandler))
//ws.Route(ws.POST("/token")) //ws.Route(ws.POST("/token"))
//ws.Route(ws.POST("/callback/{callback}")) ws.Route(ws.GET("/callback/{callback}").
To(handler.OAuthCallBackHandler))
c.Add(ws) c.Add(ws)
......
...@@ -16,22 +16,30 @@ ...@@ -16,22 +16,30 @@
* / * /
*/ */
package oauth package serverconfig
import "golang.org/x/oauth2" import (
"github.com/emicklei/go-restful"
apiserverconfig "kubesphere.io/kubesphere/pkg/apiserver/config"
)
type SimpleConfigManager struct { func AddToContainer(c *restful.Container, config *apiserverconfig.Config) error {
} configs := &restful.WebService{}
configs.Path("/server/configs").
Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON)
// information about the authorization server are published.
configs.Route(configs.GET("/oauth-configz").To(func(request *restful.Request, response *restful.Response) {
response.WriteEntity(config.AuthenticationOptions.OAuthOptions)
}))
// information about the server configuration
configs.Route(configs.GET("/configz").To(func(request *restful.Request, response *restful.Response) {
response.WriteAsJson(config.ToMap())
}))
func (s *SimpleConfigManager) Load(clientId string) (*oauth2.Config, error) { c.Add(configs)
if clientId == "kubesphere-console-client" { return nil
return &oauth2.Config{
ClientID: "8b21fef43889a28f2bd6",
ClientSecret: "xb21fef43889a28f2bd6",
Endpoint: oauth2.Endpoint{AuthURL: "http://ks-apiserver.kubesphere-system.svc/oauth/authorize", TokenURL: "http://ks-apiserver.kubesphere.io/oauth/token"},
RedirectURL: "http://ks-console.kubesphere-system.svc/oauth/token/implicit",
Scopes: nil,
}, nil
}
return nil, ConfigNotFound
} }
...@@ -133,7 +133,9 @@ func NewFakeAMOperator() *FakeOperator { ...@@ -133,7 +133,9 @@ func NewFakeAMOperator() *FakeOperator {
}) })
operator.saveFakeRole(platformRoleCacheKey(user.Anonymous), FakeRole{ operator.saveFakeRole(platformRoleCacheKey(user.Anonymous), FakeRole{
Name: "admin", Name: "admin",
Rego: "package authz\ndefault allow = false", Rego: `package authz
default allow = false
`,
}) })
return operator return operator
} }
/*
Copyright 2019 The KubeSphere 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 kubesphere
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api/devops/v1alpha2"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models"
"net/http"
"strings"
)
type Interface interface {
CreateGroup(group *models.Group) (*models.Group, error)
UpdateGroup(group *models.Group) (*models.Group, error)
DescribeGroup(name string) (*models.Group, error)
DeleteGroup(name string) error
ListUsers() (*models.PageableResponse, error)
ListWorkspaceDevOpsProjects(workspace string) (*v1alpha2.PageableDevOpsProject, error)
DeleteWorkspaceDevOpsProjects(workspace, devops string) error
}
type Client struct {
client *http.Client
apiServer string
accountServer string
}
func NewKubeSphereClient(options *Options) *Client {
return &Client{
client: &http.Client{},
apiServer: options.APIServer,
accountServer: options.AccountServer,
}
}
type Error struct {
status int
message string
}
func (e Error) Error() string {
return fmt.Sprintf("status: %d,message: %s", e.status, e.message)
}
func (c *Client) CreateGroup(group *models.Group) (*models.Group, error) {
data, err := json.Marshal(group)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/kapis/iam.kubesphere.io/v1alpha2/groups", c.accountServer), bytes.NewReader(data))
if err != nil {
klog.Error(err)
return nil, err
}
req.Header.Add("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
klog.Error(err)
return nil, err
}
defer resp.Body.Close()
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
klog.Error(err)
return nil, err
}
if resp.StatusCode > http.StatusOK {
return nil, Error{resp.StatusCode, string(data)}
}
err = json.Unmarshal(data, group)
if err != nil {
klog.Error(err)
return nil, err
}
return group, nil
}
func (c *Client) UpdateGroup(group *models.Group) (*models.Group, error) {
data, err := json.Marshal(group)
if err != nil {
klog.Error(err)
return nil, err
}
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/kapis/iam.kubesphere.io/v1alpha2/groups/%s", c.accountServer, group.Name), bytes.NewReader(data))
if err != nil {
klog.Error(err)
return nil, err
}
req.Header.Add("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
klog.Error(err)
return nil, err
}
if resp.StatusCode > http.StatusOK {
return nil, Error{resp.StatusCode, string(data)}
}
err = json.Unmarshal(data, group)
if err != nil {
klog.Error(err)
return nil, err
}
return group, nil
}
func (c *Client) DeleteGroup(name string) error {
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/kapis/iam.kubesphere.io/v1alpha2/groups/%s", c.accountServer, name), nil)
if err != nil {
klog.Error(err)
return err
}
resp, err := c.client.Do(req)
if err != nil {
klog.Error(err)
return err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
klog.Error(err)
return err
}
if resp.StatusCode > http.StatusOK {
return Error{resp.StatusCode, string(data)}
}
return nil
}
func (c *Client) DescribeGroup(name string) (*models.Group, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/kapis/iam.kubesphere.io/v1alpha2/groups/%s", c.accountServer, name), nil)
if err != nil {
klog.Error(err)
return nil, err
}
resp, err := c.client.Do(req)
if err != nil {
klog.Error(err)
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
klog.Error(err)
return nil, err
}
if resp.StatusCode > http.StatusOK {
return nil, Error{resp.StatusCode, string(data)}
}
var group models.Group
err = json.Unmarshal(data, &group)
if err != nil {
klog.Error(err)
return nil, err
}
return &group, nil
}
func (c *Client) ListUsers() (*models.PageableResponse, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/kapis/iam.kubesphere.io/v1alpha2/users", c.accountServer), nil)
if err != nil {
return nil, err
}
req.Header.Add("Authorization", c.accountServer)
resp, err := c.client.Do(req)
if err != nil {
klog.Error(err)
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
klog.Error(err)
return nil, err
}
if resp.StatusCode > http.StatusOK {
return nil, Error{resp.StatusCode, string(data)}
}
var result models.PageableResponse
err = json.Unmarshal(data, &result)
if err != nil {
klog.Error(err)
return nil, err
}
return &result, nil
}
func (c *Client) ListWorkspaceDevOpsProjects(workspace string) (*v1alpha2.PageableDevOpsProject, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/kapis/tenant.kubesphere.io/v1alpha2/workspaces/%s/devops", c.apiServer, workspace), nil)
if err != nil {
klog.Error(err)
return nil, err
}
req.Header.Add(constants.UserNameHeader, constants.AdminUserName)
klog.Info(req.Method, req.URL)
resp, err := c.client.Do(req)
if err != nil {
klog.Error(err)
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
klog.Error(err)
return nil, err
}
if resp.StatusCode > http.StatusOK {
klog.Error(req.Method, req.URL, resp.StatusCode, string(data))
return nil, Error{resp.StatusCode, string(data)}
}
var result v1alpha2.PageableDevOpsProject
err = json.Unmarshal(data, &result)
if err != nil {
klog.Error(err)
return nil, err
}
return &result, nil
}
func (c *Client) DeleteWorkspaceDevOpsProjects(workspace, devops string) error {
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/kapis/tenant.kubesphere.io/v1alpha2/workspaces/%s/devops/%s", c.apiServer, workspace, devops), nil)
if err != nil {
klog.Error(err)
return err
}
req.Header.Add(constants.UserNameHeader, constants.AdminUserName)
klog.Info(req.Method, req.URL)
resp, err := c.client.Do(req)
if err != nil {
klog.Error(err)
return err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
klog.Error(err)
return err
}
if resp.StatusCode > http.StatusOK {
klog.Error(req.Method, req.URL, resp.StatusCode, string(data))
return Error{resp.StatusCode, string(data)}
}
return nil
}
func IsNotFound(err error) bool {
if e, ok := err.(Error); ok {
if e.status == http.StatusNotFound {
return true
}
if strings.Contains(e.message, "not exist") {
return true
}
if strings.Contains(e.message, "not found") {
return true
}
}
return false
}
func IsExist(err error) bool {
if e, ok := err.(Error); ok {
if e.status == http.StatusConflict {
return true
}
if strings.Contains(e.message, "Already Exists") {
return true
}
}
return false
}
package kubesphere
import "github.com/spf13/pflag"
type Options struct {
APIServer string `json:"apiServer" yaml:"apiServer"`
AccountServer string `json:"accountServer" yaml:"accountServer"`
}
// NewKubeSphereOptions create a default options
func NewKubeSphereOptions() *Options {
return &Options{
APIServer: "http://ks-apiserver.kubesphere-system.svc",
AccountServer: "http://ks-account.kubesphere-system.svc",
}
}
func (s *Options) ApplyTo(options *Options) {
if s.AccountServer != "" {
options.AccountServer = s.AccountServer
}
if s.APIServer != "" {
options.APIServer = s.APIServer
}
}
func (s *Options) Validate() []error {
errs := []error{}
return errs
}
func (s *Options) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.APIServer, "kubesphere-apiserver-host", s.APIServer, ""+
"KubeSphere apiserver host address.")
fs.StringVar(&s.AccountServer, "kubesphere-account-host", s.AccountServer, ""+
"KubeSphere account server host address.")
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册