为了减少管理成本,考虑利用oauth2 协议实现kubepshere和内部自建gitlab的单点登录功能
翻看kubesphere的官网文档,kubesphere3.1是支持oauth2登录功能的,并且已经内置了github和阿里云IDaas的插件,只需在ks-apiserver的配置文件中稍作更改,即可实现;但是如果需要支持基于oauth2的其他第三方认证系统,就需要开发相应的插件了,要单独开发的话,似乎就违背了方便快捷实现的初衷了
转念一想,既然gitlab和github"同宗同姓",那么github的配置是不是也可同样应用在gitlab上?于是乎投机取巧的使用github的配置代替,几番折腾下来,最终还是以失败收场,看来只能老老实实的去开发插件了
下载kubesphere源码,认证插件目录位置 /pkg/apiserver/authentication/identityprovider/
接口定义
kubesphere官方已对OAuth2认证逻辑进行了抽象和封装,按照官方指引,provider只需要实现几个规定的接口即可
接口实现
具体实现参考了github插件的源码
没有大的改动,主要的变动是需要重新定义一个结构体gitlabIdentity,用以反序列化gitlab user api的用户信息
package gitlab
import (
"context"
"crypto/tls"
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/mitchellh/mapstructure"
"golang.org/x/oauth2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
)
func init() {
identityprovider.RegisterOAuthProvider(&gitlabProviderFactory{})
}
type gitlab 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 gitlab.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"`
// Used to turn off TLS certificate checks
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
// Scope specifies optional requested permissions.
Scopes []string `json:"scopes" yaml:"scopes"`
Config *oauth2.Config `json:"-" yaml:"-"`
}
// 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"`
UserInfoURL string `json:"userInfoURL" yaml:"userInfoURL"`
}
type Identity struct {
Provider string `json:"provider"`
ExternUid string `json:"extern_uid"`
}
// 根据gitab user 接口返回字段定义结构体
type gitlabIdentity struct {
ID int `json:"id"`
UserName string `json:"username"`
Email string `json:"email"`
Name string `json:"name"`
State string `json:"state"`
AvatarURL string `json:"avatar_url"`
WEBURL string `json:"web_url"`
CreatedAt time.Time `json:"created_at"`
IsAdmin bool `json:"is_admin"`
Bio string `json:"bio"`
Location string `json:"location"`
Skype string `json:"skype"`
LINKEDIN string `json:"linkedin"`
TWITTER string `json:"twitter"`
WebsiteURL string `json:"website_url"`
ORGANIZATION string `json:"organization"`
LastSignInAt time.Time `json:"last_sign_in_at"`
ConfirmedAt time.Time `json:"confirmed_at"`
ThemeID int `json:"theme_id"`
ColorSchemeID int `json:"color_scheme_id"`
ProjectsLimits int `json:"projects_limit"`
CurrentSignInAt time.Time `json:"current_sign_in_at"`
CanCreateGroup bool `json:"can_create_group"`
CanCreateProject bool `json:"can_create_project"`
TwoFactorEnabled bool `json:"two_factor_enabled"`
External bool `json:"external"`
Identities []Identity `json:"identities"`
}
type gitlabProviderFactory struct {
}
func (g *gitlabProviderFactory) Type() string {
return "GitLabIdentityProvider"
}
func (g *gitlabProviderFactory) Create(options oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
var gitlab gitlab
if err := mapstructure.Decode(options, &gitlab); err != nil {
return nil, err
}
// fixed options
options["endpoint"] = oauth.DynamicOptions{
"authURL": gitlab.Endpoint.AuthURL,
"tokenURL": gitlab.Endpoint.TokenURL,
"userInfoURL": gitlab.Endpoint.UserInfoURL,
}
gitlab.Config = &oauth2.Config{
ClientID: gitlab.ClientID,
ClientSecret: gitlab.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: gitlab.Endpoint.AuthURL,
TokenURL: gitlab.Endpoint.TokenURL,
},
RedirectURL: gitlab.RedirectURL,
Scopes: gitlab.Scopes,
}
return &gitlab, nil
}
func (g gitlabIdentity) GetUserID() string {
return g.UserName
}
func (g gitlabIdentity) GetUsername() string {
return g.UserName
}
func (g gitlabIdentity) GetEmail() string {
return g.Email
}
// 请求oauth2服务端,反序列化用户信息
func (g *gitlab) IdentityExchange(code string) (identityprovider.Identity, error) {
ctx := context.TODO()
if g.InsecureSkipVerify {
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
ctx = context.WithValue(ctx, oauth2.HTTPClient, client)
}
token, err := g.Config.Exchange(ctx, code)
if err != nil {
return nil, err
}
resp, err := oauth2.NewClient(ctx, oauth2.StaticTokenSource(token)).Get(g.Endpoint.UserInfoURL)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var gitlabIdentity gitlabIdentity
err = json.Unmarshal(data, &gitlabIdentity)
if err != nil {
return nil, err
}
return gitlabIdentity, nil
}
复制代码
改动源码后,按照kubesphere的二次开发文档重新编译生成镜像并部署
扫描二维码关注公众号,回复:
13165584 查看本文章

配置文件
相对应的,ks-apiserver的配置文件kubesphere-config也要做相应的配置
kubesphere.yaml: |
authentication:
authenticateRateLimiterMaxTries: 10
authenticateRateLimiterDuration: 10m0s
jwtSecret: "xxxxxxxxxxxxxxxxx"
oauthOptions:
accessTokenMaxAge: 1h
accessTokenInactivityTimeout: 30m
identityProviders:
- name: gitlab
type: GitLabIdentityProvider
mappingMethod: auto
provider:
clientID: 'xxxxxxxxxxxxxxxx'
clientSecret: 'xxxxxxxxxxxx'
endpoint:
# xxx gitlab地址
authURL: 'http://xxx/oauth/authorize'
tokenURL: 'http://xxx/oauth/token'
userInfoURL: 'http://xxx/api/v4/user'
redirectURL: 'http://xxx/oauth/redirect/gitlab'
scopes:
- read_user
复制代码