未验证 提交 1e5fdea5 编写于 作者: K KubeSphere CI Bot 提交者: GitHub

Merge pull request #3140 from wansir/identity-provider

improve identity provider plugin
......@@ -3775,7 +3775,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/auth.LoginRequest"
"$ref": "#/definitions/oauth.LoginRequest"
}
}
],
......@@ -4524,7 +4524,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/iam.PasswordReset"
"$ref": "#/definitions/v1alpha2.PasswordReset"
}
},
{
......@@ -12627,7 +12627,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/auth.TokenReview"
"$ref": "#/definitions/oauth.TokenReview"
}
}
],
......@@ -12635,7 +12635,7 @@
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/auth.TokenReview"
"$ref": "#/definitions/oauth.TokenReview"
}
}
}
......@@ -13296,71 +13296,6 @@
}
}
},
"auth.LoginRequest": {
"required": [
"username",
"password"
],
"properties": {
"password": {
"description": "password",
"type": "string"
},
"username": {
"description": "username",
"type": "string"
}
}
},
"auth.Spec": {
"required": [
"token"
],
"properties": {
"token": {
"description": "access token",
"type": "string"
}
}
},
"auth.Status": {
"required": [
"authenticated"
],
"properties": {
"authenticated": {
"description": "is authenticated",
"type": "boolean"
},
"user": {
"description": "user info",
"type": "object"
}
}
},
"auth.TokenReview": {
"required": [
"apiVersion",
"kind"
],
"properties": {
"apiVersion": {
"description": "Kubernetes API version",
"type": "string"
},
"kind": {
"description": "kind of the API object",
"type": "string"
},
"spec": {
"$ref": "#/definitions/auth.Spec"
},
"status": {
"description": "token review status",
"$ref": "#/definitions/auth.Status"
}
}
},
"big.Int": {
"required": [
"neg",
......@@ -15275,20 +15210,6 @@
}
}
},
"iam.PasswordReset": {
"required": [
"currentPassword",
"password"
],
"properties": {
"currentPassword": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"inf.Dec": {
"required": [
"unscaled",
......@@ -15674,6 +15595,48 @@
}
}
},
"oauth.LoginRequest": {
"required": [
"username",
"password"
],
"properties": {
"password": {
"description": "password",
"type": "string"
},
"username": {
"description": "username",
"type": "string"
}
}
},
"oauth.Spec": {
"required": [
"token"
],
"properties": {
"token": {
"description": "access token",
"type": "string"
}
}
},
"oauth.Status": {
"required": [
"authenticated"
],
"properties": {
"authenticated": {
"description": "is authenticated",
"type": "boolean"
},
"user": {
"description": "user info",
"type": "object"
}
}
},
"oauth.Token": {
"required": [
"access_token"
......@@ -15694,6 +15657,29 @@
}
}
},
"oauth.TokenReview": {
"required": [
"apiVersion",
"kind"
],
"properties": {
"apiVersion": {
"description": "Kubernetes API version",
"type": "string"
},
"kind": {
"description": "kind of the API object",
"type": "string"
},
"spec": {
"$ref": "#/definitions/oauth.Spec"
},
"status": {
"description": "token review status",
"$ref": "#/definitions/oauth.Status"
}
}
},
"openpitrix.App": {
"required": [
"category_set"
......@@ -21672,6 +21658,20 @@
}
}
},
"v1alpha2.PasswordReset": {
"required": [
"currentPassword",
"password"
],
"properties": {
"currentPassword": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"v1alpha2.Row": {
"required": [
"id",
......
......@@ -34,6 +34,7 @@ import (
"kubesphere.io/kubesphere/pkg/controller/group"
"kubesphere.io/kubesphere/pkg/controller/groupbinding"
"kubesphere.io/kubesphere/pkg/controller/job"
"kubesphere.io/kubesphere/pkg/controller/loginrecord"
"kubesphere.io/kubesphere/pkg/controller/network/ippool"
"kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy"
"kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy/provider"
......@@ -208,19 +209,19 @@ func addControllers(
go fedWorkspaceRoleBindingCacheController.Run(stopCh)
}
userController := user.NewUserController(client.Kubernetes(), client.KubeSphere(),
client.Config(),
userController := user.NewUserController(client.Kubernetes(), client.KubeSphere(), client.Config(),
kubesphereInformer.Iam().V1alpha2().Users(),
fedUserCache, fedUserCacheController,
kubesphereInformer.Iam().V1alpha2().LoginRecords(),
fedUserCache, fedUserCacheController,
kubernetesInformer.Core().V1().ConfigMaps(),
ldapClient, devopsClient,
authenticationOptions, multiClusterEnabled)
loginRecordController := user.NewLoginRecordController(
loginRecordController := loginrecord.NewLoginRecordController(
client.Kubernetes(),
client.KubeSphere(),
kubesphereInformer.Iam().V1alpha2().LoginRecords(),
kubesphereInformer.Iam().V1alpha2().Users(),
authenticationOptions.LoginHistoryRetentionPeriod)
csrController := certificatesigningrequest.NewController(client.Kubernetes(),
......
......@@ -35,7 +35,7 @@ spec:
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
groupRef:
description: Ref defines the desired relation of GroupBinding
description: GroupRef defines the desired relation of GroupBinding
properties:
apiGroup:
type: string
......
......@@ -24,7 +24,8 @@ spec:
plural: users
singular: user
scope: Cluster
subresources: {}
subresources:
status: {}
validation:
openAPIV3Schema:
description: User is the Schema for the users API
......
此差异已折叠。
......@@ -3,7 +3,6 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6 h1:tW+ztA4A9UT9xnco5wUjW1oNi35k22eUEn9tNpPYVwE=
code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc=
......@@ -60,7 +59,6 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU
github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
......@@ -228,26 +226,16 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/flect v0.1.5 h1:xpKq9ap8MbYfhuPCF0dBH854Gp9CxZjr/IocxELFflo=
github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
......@@ -309,35 +297,25 @@ github.com/grpc-ecosystem/grpc-gateway v1.11.3/go.mod h1:vNeuVxBJEsws4ogUvrchl83
github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU=
github.com/hashicorp/consul/sdk v0.4.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
......@@ -374,7 +352,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
......@@ -383,7 +360,6 @@ github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT
github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
......@@ -438,8 +414,6 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
......@@ -457,7 +431,6 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
......@@ -479,7 +452,6 @@ github.com/openshift/generic-admission-server v1.14.0/go.mod h1:GD9KN/W4KxqRQGVM
github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
......@@ -533,17 +505,13 @@ github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
......
......@@ -18,6 +18,7 @@ package api
import (
"github.com/emicklei/go-restful"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog"
"net/http"
"runtime"
......@@ -28,32 +29,49 @@ import (
var sanitizer = strings.NewReplacer(`&`, "&amp;", `<`, "&lt;", `>`, "&gt;")
func HandleInternalError(response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(1)
klog.Errorf("%s:%d %v", fn, line, err)
http.Error(response, sanitizer.Replace(err.Error()), http.StatusInternalServerError)
handle(http.StatusInternalServerError, response, req, err)
}
// HandleBadRequest writes http.StatusBadRequest and log error
func HandleBadRequest(response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(1)
klog.Errorf("%s:%d %v", fn, line, err)
http.Error(response, sanitizer.Replace(err.Error()), http.StatusBadRequest)
handle(http.StatusBadRequest, response, req, err)
}
func HandleNotFound(response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(1)
klog.Errorf("%s:%d %v", fn, line, err)
http.Error(response, sanitizer.Replace(err.Error()), http.StatusNotFound)
handle(http.StatusNotFound, response, req, err)
}
func HandleForbidden(response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(1)
klog.Errorf("%s:%d %v", fn, line, err)
http.Error(response, sanitizer.Replace(err.Error()), http.StatusForbidden)
handle(http.StatusForbidden, response, req, err)
}
func HandleUnauthorized(response *restful.Response, req *restful.Request, err error) {
handle(http.StatusUnauthorized, response, req, err)
}
func HandleTooManyRequests(response *restful.Response, req *restful.Request, err error) {
handle(http.StatusTooManyRequests, response, req, err)
}
func HandleConflict(response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(1)
handle(http.StatusConflict, response, req, err)
}
func HandleError(response *restful.Response, req *restful.Request, err error) {
var statusCode int
switch t := err.(type) {
case errors.APIStatus:
statusCode = int(t.Status().Code)
case restful.ServiceError:
statusCode = t.Code
default:
statusCode = http.StatusInternalServerError
}
handle(statusCode, response, req, err)
}
func handle(statusCode int, response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(2)
klog.Errorf("%s:%d %v", fn, line, err)
http.Error(response, sanitizer.Replace(err.Error()), http.StatusConflict)
http.Error(response, sanitizer.Replace(err.Error()), statusCode)
}
......@@ -58,13 +58,20 @@ const (
GlobalRoleAnnotation = "iam.kubesphere.io/globalrole"
WorkspaceRoleAnnotation = "iam.kubesphere.io/workspacerole"
ClusterRoleAnnotation = "iam.kubesphere.io/clusterrole"
UninitializedAnnotation = "iam.kubesphere.io/uninitialized"
RoleAnnotation = "iam.kubesphere.io/role"
RoleTemplateLabel = "iam.kubesphere.io/role-template"
ScopeLabelFormat = "scope.kubesphere.io/%s"
UserReferenceLabel = "iam.kubesphere.io/user-ref"
IdentifyProviderLabel = "iam.kubesphere.io/identify-provider"
PasswordEncryptedAnnotation = "iam.kubesphere.io/password-encrypted"
OriginUIDLabel = "iam.kubesphere.io/origin-uid"
FieldEmail = "email"
ExtraEmail = FieldEmail
ExtraIdentityProvider = "idp"
ExtraUID = "uid"
ExtraUsername = "username"
ExtraDisplayName = "displayName"
ExtraUninitialized = "uninitialized"
InGroup = "ingroup"
NotInGroup = "notingroup"
AggregateTo = "aggregateTo"
......@@ -76,6 +83,8 @@ const (
NamespaceAdmin = "admin"
WorkspaceAdminFormat = "%s-admin"
ClusterAdmin = "cluster-admin"
PreRegistrationUser = "system:pre-registration"
PreRegistrationUserGroup = "pre-registration"
)
// +genclient
......@@ -87,6 +96,7 @@ const (
// +kubebuilder:printcolumn:name="Email",type="string",JSONPath=".spec.email"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state"
// +kubebuilder:resource:categories="iam",scope="Cluster"
// +kubebuilder:subresource:status
type User struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
......@@ -126,7 +136,7 @@ const (
UserActive UserState = "Active"
// UserDisabled means the user is disabled.
UserDisabled UserState = "Disabled"
// UserDisabled means the user is disabled.
// UserAuthLimitExceeded means restrict user login.
UserAuthLimitExceeded UserState = "AuthLimitExceeded"
AuthenticatedSuccessfully = "authenticated successfully"
......@@ -136,7 +146,7 @@ const (
type UserStatus struct {
// The user status
// +optional
State UserState `json:"state,omitempty"`
State *UserState `json:"state,omitempty"`
// +optional
Reason string `json:"reason,omitempty"`
// +optional
......
......@@ -20,6 +20,10 @@ import (
"bytes"
"context"
"fmt"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/rbac"
"kubesphere.io/kubesphere/pkg/models/auth"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/loginrecord"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/user"
"net/http"
rt "runtime"
"time"
......@@ -167,9 +171,15 @@ func (s *APIServer) PrepareRun(stopCh <-chan struct{}) error {
// Installation happens before all informers start to cache objects, so
// any attempt to list objects using listers will get empty results.
func (s *APIServer) installKubeSphereAPIs() {
imOperator := im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory, s.Config.AuthenticationOptions)
amOperator := am.NewOperator(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes())
rbacAuthorizer := authorizerfactory.NewRBACAuthorizer(amOperator)
imOperator := im.NewOperator(s.KubernetesClient.KubeSphere(),
user.New(s.InformerFactory.KubeSphereSharedInformerFactory(),
s.InformerFactory.KubernetesSharedInformerFactory()),
loginrecord.New(s.InformerFactory.KubeSphereSharedInformerFactory()),
s.Config.AuthenticationOptions)
amOperator := am.NewOperator(s.KubernetesClient.KubeSphere(),
s.KubernetesClient.Kubernetes(),
s.InformerFactory)
rbacAuthorizer := rbac.NewRBACAuthorizer(amOperator)
urlruntime.Must(configv1alpha2.AddToContainer(s.container, s.Config))
urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory))
......@@ -187,20 +197,22 @@ func (s *APIServer) installKubeSphereAPIs() {
s.Config.MultiClusterOptions.ProxyPublishService,
s.Config.MultiClusterOptions.ProxyPublishAddress,
s.Config.MultiClusterOptions.AgentImage))
urlruntime.Must(iamapi.AddToContainer(s.container, imOperator,
am.NewOperator(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes()),
urlruntime.Must(iamapi.AddToContainer(s.container, imOperator, amOperator,
group.New(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes()),
s.Config.AuthenticationOptions))
rbacAuthorizer))
urlruntime.Must(oauth.AddToContainer(s.container, imOperator,
im.NewTokenOperator(
auth.NewTokenOperator(
s.CacheClient,
s.Config.AuthenticationOptions),
im.NewPasswordAuthenticator(
auth.NewPasswordAuthenticator(
s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister(),
s.Config.AuthenticationOptions),
im.NewLoginRecorder(s.KubernetesClient.KubeSphere()),
auth.NewOAuth2Authenticator(s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister(),
s.Config.AuthenticationOptions),
auth.NewLoginRecorder(s.KubernetesClient.KubeSphere()),
s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
urlruntime.Must(networkv1alpha2.AddToContainer(s.container, s.Config.NetworkOptions.WeaveScopeHost))
......@@ -211,7 +223,7 @@ func (s *APIServer) installKubeSphereAPIs() {
s.KubernetesClient.KubeSphere(),
s.S3Client,
s.Config.DevopsOptions.Host,
am.NewOperator(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes())))
amOperator))
urlruntime.Must(devopsv1alpha3.AddToContainer(s.container,
s.DevopsClient,
s.KubernetesClient.Kubernetes(),
......@@ -285,7 +297,7 @@ func (s *APIServer) buildHandlerChain(stopCh <-chan struct{}) {
excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*", "/kapis/version"}
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
amOperator := am.NewReadOnlyOperator(s.InformerFactory)
authorizers = unionauthorizer.New(pathAuthorizer, authorizerfactory.NewRBACAuthorizer(amOperator))
authorizers = unionauthorizer.New(pathAuthorizer, rbac.NewRBACAuthorizer(amOperator))
}
handler = filters.WithAuthorization(handler, authorizers)
......@@ -295,12 +307,16 @@ func (s *APIServer) buildHandlerChain(stopCh <-chan struct{}) {
handler = filters.WithMultipleClusterDispatcher(handler, clusterDispatcher)
}
loginRecorder := im.NewLoginRecorder(s.KubernetesClient.KubeSphere())
loginRecorder := auth.NewLoginRecorder(s.KubernetesClient.KubeSphere())
// authenticators are unordered
authn := unionauth.New(anonymous.NewAuthenticator(),
basictoken.New(basic.NewBasicAuthenticator(im.NewPasswordAuthenticator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister(), s.Config.AuthenticationOptions))),
bearertoken.New(jwttoken.NewTokenAuthenticator(im.NewTokenOperator(s.CacheClient, s.Config.AuthenticationOptions), s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister())))
handler = filters.WithAuthentication(handler, authn, loginRecorder)
basictoken.New(basic.NewBasicAuthenticator(auth.NewPasswordAuthenticator(s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister(),
s.Config.AuthenticationOptions), loginRecorder)),
bearertoken.New(jwttoken.NewTokenAuthenticator(auth.NewTokenOperator(s.CacheClient,
s.Config.AuthenticationOptions),
s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister())))
handler = filters.WithAuthentication(handler, authn)
handler = filters.WithRequestInfo(handler, requestInfoResolver)
s.Server.Handler = handler
}
......
......@@ -18,10 +18,13 @@ package basic
import (
"context"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/models/auth"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"kubesphere.io/kubesphere/pkg/models/iam/im"
)
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic.
......@@ -30,28 +33,36 @@ import (
// and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
// because some resources are public accessible.
type basicAuthenticator struct {
authenticator im.PasswordAuthenticator
authenticator auth.PasswordAuthenticator
loginRecorder auth.LoginRecorder
}
func NewBasicAuthenticator(authenticator im.PasswordAuthenticator) authenticator.Password {
func NewBasicAuthenticator(authenticator auth.PasswordAuthenticator, loginRecorder auth.LoginRecorder) authenticator.Password {
return &basicAuthenticator{
authenticator: authenticator,
loginRecorder: loginRecorder,
}
}
func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
providedUser, err := t.authenticator.Authenticate(username, password)
authenticated, provider, err := t.authenticator.Authenticate(username, password)
if err != nil {
if t.loginRecorder != nil && err == auth.IncorrectPasswordError {
var sourceIP, userAgent string
if requestInfo, ok := request.RequestInfoFrom(ctx); ok {
sourceIP = requestInfo.SourceIP
userAgent = requestInfo.UserAgent
}
if err := t.loginRecorder.RecordLogin(username, iamv1alpha2.BasicAuth, provider, sourceIP, userAgent, err); err != nil {
klog.Errorf("Failed to record unsuccessful login attempt for user %s, error: %v", username, err)
}
}
return nil, false, err
}
return &authenticator.Response{
User: &user.DefaultInfo{
Name: providedUser.GetName(),
UID: providedUser.GetUID(),
Groups: append(providedUser.GetGroups(), user.AllAuthenticated),
Name: authenticated.GetName(),
Groups: append(authenticated.GetGroups(), user.AllAuthenticated),
},
}, true, nil
}
......@@ -18,13 +18,13 @@ package jwttoken
import (
"context"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/auth"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/iam/im"
)
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic.
......@@ -33,11 +33,11 @@ import (
// and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
// because some resources are public accessible.
type tokenAuthenticator struct {
tokenOperator im.TokenManagementInterface
tokenOperator auth.TokenManagementInterface
userLister iamv1alpha2listers.UserLister
}
func NewTokenAuthenticator(tokenOperator im.TokenManagementInterface, userLister iamv1alpha2listers.UserLister) authenticator.Token {
func NewTokenAuthenticator(tokenOperator auth.TokenManagementInterface, userLister iamv1alpha2listers.UserLister) authenticator.Token {
return &tokenAuthenticator{
tokenOperator: tokenOperator,
userLister: userLister,
......@@ -51,6 +51,16 @@ func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string
return nil, false, err
}
if providedUser.GetName() == iamv1alpha2.PreRegistrationUser {
return &authenticator.Response{
User: &user.DefaultInfo{
Name: providedUser.GetName(),
Extra: providedUser.GetExtra(),
Groups: providedUser.GetGroups(),
},
}, true, nil
}
dbUser, err := t.userLister.Get(providedUser.GetName())
if err != nil {
return nil, false, err
......@@ -58,8 +68,7 @@ func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string
return &authenticator.Response{
User: &user.DefaultInfo{
Name: providedUser.GetName(),
UID: providedUser.GetUID(),
Name: dbUser.GetName(),
Groups: append(dbUser.Spec.Groups, user.AllAuthenticated),
},
}, true, nil
......
......@@ -20,15 +20,19 @@ import (
"context"
"encoding/json"
"errors"
"github.com/mitchellh/mapstructure"
"io/ioutil"
"golang.org/x/oauth2"
"gopkg.in/yaml.v3"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
)
type AliyunIDaaS struct {
func init() {
identityprovider.RegisterOAuthProvider(&idaasProviderFactory{})
}
type aliyunIDaaS struct {
// ClientID is the application's ID.
ClientID string `json:"clientID" yaml:"clientID"`
......@@ -39,7 +43,7 @@ type AliyunIDaaS struct {
// 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"`
Endpoint endpoint `json:"endpoint" yaml:"endpoint"`
// RedirectURL is the URL to redirect users going through
// the OAuth flow, after the resource owner's URLs.
......@@ -49,15 +53,15 @@ type AliyunIDaaS struct {
Scopes []string `json:"scopes" yaml:"scopes"`
}
// Endpoint represents an OAuth 2.0 provider's authorization and token
// endpoint represents an OAuth 2.0 provider's authorization and token
// endpoint URLs.
type Endpoint struct {
type endpoint struct {
AuthURL string `json:"authURL" yaml:"authURL"`
TokenURL string `json:"tokenURL" yaml:"tokenURL"`
UserInfoURL string `json:"user_info_url" yaml:"userInfoUrl"`
}
type IDaaSIdentity struct {
type idaasIdentity struct {
Sub string `json:"sub"`
OuID string `json:"ou_id"`
Nickname string `json:"nickname"`
......@@ -67,72 +71,73 @@ type IDaaSIdentity struct {
Username string `json:"username"`
}
type UserInfoResp struct {
type userInfoResp struct {
Success bool `json:"success"`
Message string `json:"message"`
Code string `json:"code"`
IDaaSIdentity IDaaSIdentity `json:"data"`
IDaaSIdentity idaasIdentity `json:"data"`
}
func init() {
identityprovider.RegisterOAuthProvider(&AliyunIDaaS{})
type idaasProviderFactory struct {
}
func (a *AliyunIDaaS) Type() string {
func (g *idaasProviderFactory) Type() string {
return "AliyunIDaasProvider"
}
func (a *AliyunIDaaS) Setup(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
data, err := yaml.Marshal(options)
if err != nil {
return nil, err
}
var provider AliyunIDaaS
err = yaml.Unmarshal(data, &provider)
if err != nil {
func (g *idaasProviderFactory) Create(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
var idaas aliyunIDaaS
if err := mapstructure.Decode(options, &idaas); err != nil {
return nil, err
}
return &provider, nil
return &idaas, nil
}
func (a IDaaSIdentity) GetName() string {
func (a idaasIdentity) GetUserID() string {
return a.Sub
}
func (a idaasIdentity) GetUsername() string {
return a.Username
}
func (a IDaaSIdentity) GetEmail() string {
func (a idaasIdentity) GetEmail() string {
return a.Email
}
func (g *AliyunIDaaS) IdentityExchange(code string) (identityprovider.Identity, error) {
func (a idaasIdentity) GetDisplayName() string {
return a.Nickname
}
func (a *aliyunIDaaS) IdentityExchange(code string) (identityprovider.Identity, error) {
config := oauth2.Config{
ClientID: g.ClientID,
ClientSecret: g.ClientSecret,
ClientID: a.ClientID,
ClientSecret: a.ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: g.Endpoint.AuthURL,
TokenURL: g.Endpoint.TokenURL,
AuthURL: a.Endpoint.AuthURL,
TokenURL: a.Endpoint.TokenURL,
AuthStyle: oauth2.AuthStyleAutoDetect,
},
RedirectURL: g.RedirectURL,
Scopes: g.Scopes,
RedirectURL: a.RedirectURL,
Scopes: a.Scopes,
}
token, err := config.Exchange(context.Background(), code)
if err != nil {
return nil, err
}
resp, err := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(token)).Get(g.Endpoint.UserInfoURL)
resp, err := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(token)).Get(a.Endpoint.UserInfoURL)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return nil, err
}
defer resp.Body.Close()
var UserInfoResp UserInfoResp
var UserInfoResp userInfoResp
err = json.Unmarshal(data, &UserInfoResp)
if err != nil {
return nil, err
......
......@@ -14,38 +14,35 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
package identityprovider
import "fmt"
const (
KindTokenReview = "TokenReview"
import (
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
)
type Spec struct {
Token string `json:"token" description:"access token"`
}
var (
builtinGenericProviders = make(map[string]GenericProviderFactory)
)
type Status struct {
Authenticated bool `json:"authenticated" description:"is authenticated"`
User map[string]interface{} `json:"user,omitempty" description:"user info"`
type GenericProvider interface {
// Authenticate from remote server
Authenticate(username string, password string) (Identity, error)
}
type TokenReview struct {
APIVersion string `json:"apiVersion" description:"Kubernetes API version"`
Kind string `json:"kind" description:"kind of the API object"`
Spec *Spec `json:"spec,omitempty"`
Status *Status `json:"status,omitempty" description:"token review status"`
type GenericProviderFactory interface {
// Type unique type of the provider
Type() string
// Apply the dynamic options from kubesphere-config
Create(options *oauth.DynamicOptions) (GenericProvider, error)
}
type LoginRequest struct {
Username string `json:"username" description:"username"`
Password string `json:"password" description:"password"`
func CreateGenericProvider(providerType string, options *oauth.DynamicOptions) (GenericProvider, error) {
if factory, ok := builtinGenericProviders[providerType]; ok {
return factory.Create(options)
}
return nil, identityProviderNotFound
}
func (request *TokenReview) Validate() error {
if request.Spec == nil || request.Spec.Token == "" {
return fmt.Errorf("token must not be null")
}
return nil
func RegisterGenericProvider(factory GenericProviderFactory) {
builtinGenericProviders[factory.Type()] = factory
}
......@@ -19,8 +19,8 @@ package github
import (
"context"
"encoding/json"
"github.com/mitchellh/mapstructure"
"golang.org/x/oauth2"
"gopkg.in/yaml.v3"
"io/ioutil"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
......@@ -31,7 +31,11 @@ const (
UserInfoURL = "https://api.github.com/user"
)
type Github struct {
func init() {
identityprovider.RegisterOAuthProvider(&githubProviderFactory{})
}
type github struct {
// ClientID is the application's ID.
ClientID string `json:"clientID" yaml:"clientID"`
......@@ -41,8 +45,8 @@ type Github struct {
// 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"`
// 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.
......@@ -52,14 +56,14 @@ type Github struct {
Scopes []string `json:"scopes" yaml:"scopes"`
}
// Endpoint represents an OAuth 2.0 provider's authorization and token
// endpoint represents an OAuth 2.0 provider's authorization and token
// endpoint URLs.
type Endpoint struct {
type endpoint struct {
AuthURL string `json:"authURL" yaml:"authURL"`
TokenURL string `json:"tokenURL" yaml:"tokenURL"`
}
type GithubIdentity struct {
type githubIdentity struct {
Login string `json:"login"`
ID int `json:"id"`
NodeID string `json:"node_id"`
......@@ -98,36 +102,38 @@ type GithubIdentity struct {
Collaborators int `json:"collaborators"`
}
func init() {
identityprovider.RegisterOAuthProvider(&Github{})
type githubProviderFactory struct {
}
func (g *Github) Type() string {
func (g *githubProviderFactory) Type() string {
return "GitHubIdentityProvider"
}
func (g *Github) Setup(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
data, err := yaml.Marshal(options)
if err != nil {
func (g *githubProviderFactory) Create(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
var github github
if err := mapstructure.Decode(options, &github); err != nil {
return nil, err
}
var provider Github
err = yaml.Unmarshal(data, &provider)
if err != nil {
return nil, err
}
return &provider, nil
return &github, nil
}
func (g GithubIdentity) GetName() string {
func (g githubIdentity) GetUserID() string {
return g.Login
}
func (g GithubIdentity) GetEmail() string {
func (g githubIdentity) GetUsername() string {
return g.Login
}
func (g githubIdentity) GetEmail() string {
return g.Email
}
func (g *Github) IdentityExchange(code string) (identityprovider.Identity, error) {
func (g githubIdentity) GetDisplayName() string {
return ""
}
func (g *github) IdentityExchange(code string) (identityprovider.Identity, error) {
config := oauth2.Config{
ClientID: g.ClientID,
ClientSecret: g.ClientSecret,
......@@ -141,27 +147,23 @@ func (g *Github) IdentityExchange(code string) (identityprovider.Identity, error
}
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)
defer resp.Body.Close()
if err != nil {
return nil, err
}
defer resp.Body.Close()
var githubIdentity GithubIdentity
var githubIdentity githubIdentity
err = json.Unmarshal(data, &githubIdentity)
if err != nil {
return nil, err
}
......
......@@ -17,6 +17,12 @@ limitations under the License.
package identityprovider
type Identity interface {
GetName() string
// required
GetUserID() string
// optional
GetUsername() string
// optional
GetDisplayName() string
// optional
GetEmail() string
}
/*
Copyright 2020 The KubeSphere Authors.
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
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.
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 identityprovider
package ldap
import (
"crypto/tls"
......@@ -26,24 +24,23 @@ import (
"github.com/go-ldap/ldap"
"github.com/mitchellh/mapstructure"
"io/ioutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/constants"
"time"
)
const (
LdapIdentityProvider = "LDAPIdentityProvider"
ldapIdentityProvider = "LDAPIdentityProvider"
defaultReadTimeout = 15000
)
type LdapProvider interface {
Authenticate(username string, password string) (*iamv1alpha2.User, error)
func init() {
identityprovider.RegisterGenericProvider(&ldapProviderFactory{})
}
type ldapOptions struct {
type ldapProvider struct {
// Host and optional port of the LDAP server in the form "host:port".
// If the port is not supplied, 389 for insecure or StartTLS connections, 636
Host string `json:"host,omitempty" yaml:"managerDN"`
......@@ -73,105 +70,125 @@ type ldapOptions struct {
UserMemberAttribute string `json:"userMemberAttribute,omitempty" yaml:"userMemberAttribute"`
// Attribute on a group object storing the information for primary group membership.
GroupMemberAttribute string `json:"groupMemberAttribute,omitempty" yaml:"groupMemberAttribute"`
// login attribute used for comparing user entries.
// The following three fields are direct mappings of attributes on the user entry.
// login attribute used for comparing user entries.
LoginAttribute string `json:"loginAttribute" yaml:"loginAttribute"`
MailAttribute string `json:"mailAttribute" yaml:"mailAttribute"`
DisplayNameAttribute string `json:"displayNameAttribute" yaml:"displayNameAttribute"`
}
type ldapProvider struct {
options ldapOptions
type ldapProviderFactory struct {
}
func NewLdapProvider(options *oauth.DynamicOptions) (LdapProvider, error) {
var ldapOptions ldapOptions
if err := mapstructure.Decode(options, &ldapOptions); err != nil {
func (l *ldapProviderFactory) Type() string {
return ldapIdentityProvider
}
func (l *ldapProviderFactory) Create(options *oauth.DynamicOptions) (identityprovider.GenericProvider, error) {
var ldapProvider ldapProvider
if err := mapstructure.Decode(options, &ldapProvider); err != nil {
return nil, err
}
if ldapOptions.ReadTimeout <= 0 {
ldapOptions.ReadTimeout = defaultReadTimeout
if ldapProvider.ReadTimeout <= 0 {
ldapProvider.ReadTimeout = defaultReadTimeout
}
return &ldapProvider{options: ldapOptions}, nil
return &ldapProvider, nil
}
type ldapIdentity struct {
Username string
Email string
DisplayName string
}
func (l ldapProvider) Authenticate(username string, password string) (*iamv1alpha2.User, error) {
func (l *ldapIdentity) GetUserID() string {
return l.Username
}
func (l *ldapIdentity) GetUsername() string {
return l.Username
}
func (l *ldapIdentity) GetEmail() string {
return l.Email
}
func (l *ldapIdentity) GetDisplayName() string {
return l.DisplayName
}
func (l ldapProvider) Authenticate(username string, password string) (identityprovider.Identity, error) {
conn, err := l.newConn()
if err != nil {
klog.Error(err)
return nil, err
}
conn.SetTimeout(time.Duration(l.options.ReadTimeout) * time.Millisecond)
conn.SetTimeout(time.Duration(l.ReadTimeout) * time.Millisecond)
defer conn.Close()
err = conn.Bind(l.options.ManagerDN, l.options.ManagerPassword)
err = conn.Bind(l.ManagerDN, l.ManagerPassword)
if err != nil {
klog.Error(err)
return nil, err
}
filter := fmt.Sprintf("(&(%s=%s)%s)", l.options.LoginAttribute, username, l.options.UserSearchFilter)
filter := fmt.Sprintf("(&(%s=%s)%s)", l.LoginAttribute, username, l.UserSearchFilter)
result, err := conn.Search(&ldap.SearchRequest{
BaseDN: l.options.UserSearchBase,
BaseDN: l.UserSearchBase,
Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases,
SizeLimit: 1,
TimeLimit: 0,
TypesOnly: false,
Filter: filter,
Attributes: []string{l.options.LoginAttribute, l.options.MailAttribute, l.options.DisplayNameAttribute},
Attributes: []string{l.LoginAttribute, l.MailAttribute, l.DisplayNameAttribute},
})
if err != nil {
klog.Error(err)
return nil, err
}
if len(result.Entries) == 1 {
entry := result.Entries[0]
err = conn.Bind(entry.DN, password)
if err != nil {
klog.Error(err)
return nil, err
}
email := entry.GetAttributeValue(l.options.MailAttribute)
displayName := entry.GetAttributeValue(l.options.DisplayNameAttribute)
return &iamv1alpha2.User{
ObjectMeta: metav1.ObjectMeta{
Name: username,
Annotations: map[string]string{
constants.DisplayNameAnnotationKey: displayName,
},
},
Spec: iamv1alpha2.UserSpec{
Email: email,
DisplayName: displayName,
},
}, nil
if len(result.Entries) != 1 {
return nil, errors.NewUnauthorized("incorrect password")
}
return nil, ldap.NewError(ldap.LDAPResultNoSuchObject, fmt.Errorf("could not find user %s in LDAP directory", username))
entry := result.Entries[0]
if err = conn.Bind(entry.DN, password); err != nil {
klog.Error(err)
if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) {
return nil, errors.NewUnauthorized("incorrect password")
}
return nil, err
}
email := entry.GetAttributeValue(l.MailAttribute)
displayName := entry.GetAttributeValue(l.DisplayNameAttribute)
return &ldapIdentity{
Username: username,
DisplayName: displayName,
Email: email,
}, nil
}
func (l *ldapProvider) newConn() (*ldap.Conn, error) {
if !l.options.StartTLS {
return ldap.Dial("tcp", l.options.Host)
if !l.StartTLS {
return ldap.Dial("tcp", l.Host)
}
tlsConfig := tls.Config{}
if l.options.InsecureSkipVerify {
if l.InsecureSkipVerify {
tlsConfig.InsecureSkipVerify = true
}
tlsConfig.RootCAs = x509.NewCertPool()
var caCert []byte
var err error
// Load CA cert
if l.options.RootCA != "" {
if caCert, err = ioutil.ReadFile(l.options.RootCA); err != nil {
if l.RootCA != "" {
if caCert, err = ioutil.ReadFile(l.RootCA); err != nil {
klog.Error(err)
return nil, err
}
}
if l.options.RootCAData != "" {
if caCert, err = base64.StdEncoding.DecodeString(l.options.RootCAData); err != nil {
if l.RootCAData != "" {
if caCert, err = base64.StdEncoding.DecodeString(l.RootCAData); err != nil {
klog.Error(err)
return nil, err
}
......@@ -179,5 +196,5 @@ func (l *ldapProvider) newConn() (*ldap.Conn, error) {
if caCert != nil {
tlsConfig.RootCAs.AppendCertsFromPEM(caCert)
}
return ldap.DialTLS("tcp", l.options.Host, &tlsConfig)
return ldap.DialTLS("tcp", l.Host, &tlsConfig)
}
/*
Copyright 2020 The KubeSphere Authors.
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
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.
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 identityprovider
package ldap
import (
"github.com/google/go-cmp/cmp"
......@@ -42,12 +40,11 @@ mailAttribute: mail
if err != nil {
t.Fatal(err)
}
provider, err := NewLdapProvider(&dynamicOptions)
got, err := new(ldapProviderFactory).Create(&dynamicOptions)
if err != nil {
t.Fatal(err)
}
got := provider.(*ldapProvider).options
expected := ldapOptions{
expected := &ldapProvider{
Host: "test.sn.mynetname.net:389",
StartTLS: false,
InsecureSkipVerify: false,
......@@ -81,14 +78,14 @@ func TestLdapProvider_Authenticate(t *testing.T) {
t.Fatal(err)
}
var dynamicOptions oauth.DynamicOptions
if err := yaml.Unmarshal(options, &dynamicOptions); err != nil {
if err = yaml.Unmarshal(options, &dynamicOptions); err != nil {
t.Fatal(err)
}
provider, err := NewLdapProvider(&dynamicOptions)
ldapProvider, err := new(ldapProviderFactory).Create(&dynamicOptions)
if err != nil {
t.Fatal(err)
}
if _, err := provider.Authenticate("test", "test"); err != nil {
if _, err = ldapProvider.Authenticate("test", "test"); err != nil {
t.Fatal(err)
}
}
/*
Copyright 2020 The KubeSphere Authors.
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
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.
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 identityprovider
import (
......@@ -24,23 +21,29 @@ import (
)
var (
oauthProviders = make(map[string]OAuthProvider, 0)
ErrorIdentityProviderNotFound = errors.New("the identity provider was not found")
builtinOAuthProviders = make(map[string]OAuthProviderFactory)
identityProviderNotFound = errors.New("identity provider not found")
)
type OAuthProvider interface {
Type() string
Setup(options *oauth.DynamicOptions) (OAuthProvider, error)
// IdentityExchange exchange identity from remote server
IdentityExchange(code string) (Identity, error)
}
func GetOAuthProvider(providerType string, options *oauth.DynamicOptions) (OAuthProvider, error) {
if provider, ok := oauthProviders[providerType]; ok {
return provider.Setup(options)
type OAuthProviderFactory interface {
// Type unique type of the provider
Type() string
// Apply the dynamic options from kubesphere-config
Create(options *oauth.DynamicOptions) (OAuthProvider, error)
}
func CreateOAuthProvider(providerType string, options *oauth.DynamicOptions) (OAuthProvider, error) {
if provider, ok := builtinOAuthProviders[providerType]; ok {
return provider.Create(options)
}
return nil, ErrorIdentityProviderNotFound
return nil, identityProviderNotFound
}
func RegisterOAuthProvider(provider OAuthProvider) {
oauthProviders[provider.Type()] = provider
func RegisterOAuthProvider(factory OAuthProviderFactory) {
builtinOAuthProviders[factory.Type()] = factory
}
......@@ -35,12 +35,13 @@ const (
// 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.
// Fails if a user with that username 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.
// not supported yet.
MappingMethodMixed MappingMethod = "mixed"
)
......
......@@ -21,6 +21,7 @@ import (
"github.com/spf13/pflag"
_ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/aliyunidaas"
_ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/github"
_ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/ldap"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"time"
)
......@@ -66,7 +67,6 @@ func (options *AuthenticationOptions) Validate() []error {
if len(options.JwtSecret) == 0 {
errs = append(errs, fmt.Errorf("jwt secret is empty"))
}
return errs
}
......
......@@ -29,9 +29,10 @@ const (
)
type Claims struct {
Username string `json:"username"`
UID string `json:"uid"`
TokenType TokenType `json:"token_type"`
Username string `json:"username"`
Groups []string `json:"groups,omitempty"`
Extra map[string][]string `json:"extra,omitempty"`
TokenType TokenType `json:"token_type"`
// Currently, we are not using any field in jwt.StandardClaims
jwt.StandardClaims
}
......@@ -51,7 +52,7 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (user.Info, TokenType, error
klog.Error(err)
return nil, "", err
}
return &user.DefaultInfo{Name: clm.Username, UID: clm.UID}, clm.TokenType, nil
return &user.DefaultInfo{Name: clm.Username, Groups: clm.Groups, Extra: clm.Extra}, clm.TokenType, nil
}
func (s *jwtTokenIssuer) IssueTo(user user.Info, tokenType TokenType, expiresIn time.Duration) (string, error) {
......@@ -59,7 +60,8 @@ func (s *jwtTokenIssuer) IssueTo(user user.Info, tokenType TokenType, expiresIn
notBefore := issueAt
clm := &Claims{
Username: user.GetName(),
UID: user.GetUID(),
Groups: user.GetGroups(),
Extra: user.GetExtra(),
TokenType: tokenType,
StandardClaims: jwt.StandardClaims{
IssuedAt: issueAt,
......
......@@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
// Following code copied from k8s.io/kubernetes/pkg/apis/rbac/v1 to avoid import collision
package rbac
import (
"fmt"
......
......@@ -16,7 +16,7 @@ limitations under the License.
// NOTE: This file is copied from k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac.
package authorizerfactory
package rbac
import (
"bytes"
......@@ -36,7 +36,6 @@ import (
rbacv1 "k8s.io/api/rbac/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/authentication/user"
rbacv1helpers "kubesphere.io/kubesphere/pkg/api/rbac/v1"
)
const (
......@@ -159,14 +158,14 @@ func ruleAllows(requestAttributes authorizer.Attributes, rule *rbacv1.PolicyRule
combinedResource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource()
}
return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) &&
rbacv1helpers.APIGroupMatches(rule, requestAttributes.GetAPIGroup()) &&
rbacv1helpers.ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) &&
rbacv1helpers.ResourceNameMatches(rule, requestAttributes.GetName())
return VerbMatches(rule, requestAttributes.GetVerb()) &&
APIGroupMatches(rule, requestAttributes.GetAPIGroup()) &&
ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) &&
ResourceNameMatches(rule, requestAttributes.GetName())
}
return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) &&
rbacv1helpers.NonResourceURLMatches(rule, requestAttributes.GetPath())
return VerbMatches(rule, requestAttributes.GetVerb()) &&
NonResourceURLMatches(rule, requestAttributes.GetPath())
}
func regoPolicyAllows(requestAttributes authorizer.Attributes, regoPolicy string) bool {
......
/*
Copyright 2016 The Kubernetes 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
Copyright 2020 The KubeSphere Authors.
http://www.apache.org/licenses/LICENSE-2.0
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.
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 authorizerfactory
package rbac
import (
"errors"
......
......@@ -26,26 +26,23 @@ import (
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/models/iam/im"
"net/http"
"strings"
)
// WithAuthentication installs authentication handler to handler chain.
// The following part is a little bit ugly, WithAuthentication also logs user failed login attempt
// if using basic auth. But only treats request with requestURI `/oauth/authorize` as login attempt
func WithAuthentication(handler http.Handler, auth authenticator.Request, loginRecorder im.LoginRecorder) http.Handler {
if auth == nil {
func WithAuthentication(handler http.Handler, authRequest authenticator.Request) http.Handler {
if authRequest == nil {
klog.Warningf("Authentication is disabled")
return handler
}
s := serializer.NewCodecFactory(runtime.NewScheme()).WithoutConversion()
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
resp, ok, err := auth.AuthenticateRequest(req)
username, _, usingBasicAuth := req.BasicAuth()
resp, ok, err := authRequest.AuthenticateRequest(req)
_, _, usingBasicAuth := req.BasicAuth()
defer func() {
// if we authenticated successfully, go ahead and remove the bearer token so that no one
......@@ -56,41 +53,17 @@ func WithAuthentication(handler http.Handler, auth authenticator.Request, loginR
}()
if err != nil || !ok {
if err != nil {
klog.Errorf("Unable to authenticate the request due to error: %v", err)
if usingBasicAuth && err.Error() == im.AuthFailedIncorrectPassword.Error() { // log failed login attempts
go func(user string) {
if loginRecorder != nil && len(user) != 0 {
err = loginRecorder.RecordLogin(user, iamv1alpha2.BasicAuth, "", err, req)
klog.Errorf("Failed to record unsuccessful login attempt for user %s, error: %v", user, err)
}
}(username)
}
}
ctx := req.Context()
requestInfo, found := request.RequestInfoFrom(ctx)
if !found {
responsewriters.InternalError(w, req, errors.New("no RequestInfo found in the context"))
return
}
gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
if err != nil && err.Error() == im.AuthRateLimitExceeded.Error() {
responsewriters.ErrorNegotiated(apierrors.NewTooManyRequests(fmt.Sprintf("Unauthorized: %s", err), 60), s, gv, w, req)
} else {
responsewriters.ErrorNegotiated(apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)), s, gv, w, req)
}
responsewriters.ErrorNegotiated(apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)), s, gv, w, req)
return
}
go func() {
if loginRecorder != nil && usingBasicAuth && strings.HasPrefix(req.URL.Path, "/oauth/authorize") {
err = loginRecorder.RecordLogin(username, iamv1alpha2.BasicAuth, "", nil, req)
klog.Errorf("Failed to record unsuccessful login attempt for user %s, error: %v", username, err)
}
}()
req = req.WithContext(request.WithUser(req.Context(), resp.User))
handler.ServeHTTP(w, req)
})
......
......@@ -31,6 +31,7 @@ import (
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/constants"
netutils "kubesphere.io/kubesphere/pkg/utils/net"
"net/http"
"strings"
......@@ -74,6 +75,12 @@ type RequestInfo struct {
// Scope of requested resource.
ResourceScope string
// Source IP
SourceIP string
// User agent
UserAgent string
}
type RequestInfoFactory struct {
......@@ -119,6 +126,8 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
},
Workspace: api.WorkspaceNone,
Cluster: api.ClusterNone,
SourceIP: netutils.GetRequestIP(req),
UserAgent: req.UserAgent(),
}
defer func() {
......
/*
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 loginrecord
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
iamv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/controller/utils/controller"
"time"
)
const (
// SuccessSynced is used as part of the Event 'reason' when a Foo is synced
successSynced = "Synced"
// is synced successfully
messageResourceSynced = "LoginRecord synced successfully"
controllerName = "loginrecord-controller"
)
type loginRecordController struct {
controller.BaseController
k8sClient kubernetes.Interface
ksClient kubesphere.Interface
loginRecordLister iamv1alpha2listers.LoginRecordLister
loginRecordSynced cache.InformerSynced
userLister iamv1alpha2listers.UserLister
userSynced cache.InformerSynced
loginHistoryRetentionPeriod time.Duration
// recorder is an event recorder for recording Event resources to the
// Kubernetes API.
recorder record.EventRecorder
}
func NewLoginRecordController(k8sClient kubernetes.Interface,
ksClient kubesphere.Interface,
loginRecordInformer iamv1alpha2informers.LoginRecordInformer,
userInformer iamv1alpha2informers.UserInformer,
loginHistoryRetentionPeriod time.Duration) *loginRecordController {
klog.V(4).Info("Creating event broadcaster")
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(klog.Infof)
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")})
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerName})
ctl := &loginRecordController{
BaseController: controller.BaseController{
Workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "LoginRecords"),
Synced: []cache.InformerSynced{loginRecordInformer.Informer().HasSynced, userInformer.Informer().HasSynced},
Name: controllerName,
},
k8sClient: k8sClient,
ksClient: ksClient,
loginRecordLister: loginRecordInformer.Lister(),
userLister: userInformer.Lister(),
loginHistoryRetentionPeriod: loginHistoryRetentionPeriod,
recorder: recorder,
}
ctl.Handler = ctl.reconcile
klog.Info("Setting up event handlers")
loginRecordInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ctl.Enqueue,
UpdateFunc: func(old, new interface{}) {
ctl.Enqueue(new)
},
DeleteFunc: ctl.Enqueue,
})
return ctl
}
func (c *loginRecordController) Start(stopCh <-chan struct{}) error {
return c.Run(5, stopCh)
}
func (c *loginRecordController) reconcile(key string) error {
loginRecord, err := c.loginRecordLister.Get(key)
if err != nil {
if errors.IsNotFound(err) {
utilruntime.HandleError(fmt.Errorf("login record '%s' in work queue no longer exists", key))
return nil
}
klog.Error(err)
return err
}
if !loginRecord.ObjectMeta.DeletionTimestamp.IsZero() {
// The object is being deleted
// Our finalizer has finished, so the reconciler can do nothing.
return nil
}
if err = c.updateUserLastLoginTime(loginRecord); err != nil {
return err
}
now := time.Now()
// login record beyonds retention period
if loginRecord.CreationTimestamp.Add(c.loginHistoryRetentionPeriod).Before(now) {
if err = c.ksClient.IamV1alpha2().LoginRecords().Delete(loginRecord.Name, metav1.NewDeleteOptions(0)); err != nil {
klog.Error(err)
return err
}
} else { // put item back into the queue
c.Workqueue.AddAfter(key, loginRecord.CreationTimestamp.Add(c.loginHistoryRetentionPeriod).Sub(now))
}
c.recorder.Event(loginRecord, corev1.EventTypeNormal, successSynced, messageResourceSynced)
return nil
}
// updateUserLastLoginTime accepts a login object and set user lastLoginTime field
func (c *loginRecordController) updateUserLastLoginTime(loginRecord *iamv1alpha2.LoginRecord) error {
username, ok := loginRecord.Labels[iamv1alpha2.UserReferenceLabel]
if !ok || len(username) == 0 {
klog.V(4).Info("login doesn't belong to any user")
return nil
}
user, err := c.userLister.Get(username)
if err != nil {
// ignore not found error
if errors.IsNotFound(err) {
klog.V(4).Infof("user %s doesn't exist any more, login record will be deleted later", username)
return nil
}
klog.Error(err)
return err
}
// update lastLoginTime
if user.DeletionTimestamp.IsZero() &&
(user.Status.LastLoginTime == nil || user.Status.LastLoginTime.Before(&loginRecord.CreationTimestamp)) {
user.Status.LastLoginTime = &loginRecord.CreationTimestamp
user, err = c.ksClient.IamV1alpha2().Users().UpdateStatus(user)
return err
}
return 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 loginrecord
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
kubeinformers "k8s.io/client-go/informers"
k8sfake "k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"reflect"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
alwaysReady = func() bool { return true }
noResyncPeriodFunc = func() time.Duration { return 0 }
)
type fixture struct {
t *testing.T
ksclient *fake.Clientset
k8sclient *k8sfake.Clientset
// Objects to put in the store.
user *iamv1alpha2.User
loginRecord *iamv1alpha2.LoginRecord
// Actions expected to happen on the client.
kubeactions []core.Action
actions []core.Action
// Objects from here preloaded into NewSimpleFake.
kubeobjects []runtime.Object
objects []runtime.Object
}
func newFixture(t *testing.T) *fixture {
f := &fixture{}
f.t = t
f.objects = []runtime.Object{}
f.kubeobjects = []runtime.Object{}
return f
}
func newUser(name string) *iamv1alpha2.User {
return &iamv1alpha2.User{
TypeMeta: metav1.TypeMeta{APIVersion: iamv1alpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: iamv1alpha2.UserSpec{
Email: fmt.Sprintf("%s@kubesphere.io", name),
Lang: "zh-CN",
Description: "fake user",
},
}
}
func newLoginRecord(username string) *iamv1alpha2.LoginRecord {
return &iamv1alpha2.LoginRecord{
TypeMeta: metav1.TypeMeta{APIVersion: iamv1alpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: username,
CreationTimestamp: metav1.Now(),
Labels: map[string]string{iamv1alpha2.UserReferenceLabel: username},
},
Spec: iamv1alpha2.LoginRecordSpec{
Type: iamv1alpha2.Token,
Success: true,
Reason: "",
},
}
}
func (f *fixture) newController() (*loginRecordController, ksinformers.SharedInformerFactory, kubeinformers.SharedInformerFactory) {
f.ksclient = fake.NewSimpleClientset(f.objects...)
f.k8sclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
ksInformers := ksinformers.NewSharedInformerFactory(f.ksclient, noResyncPeriodFunc())
k8sInformers := kubeinformers.NewSharedInformerFactory(f.k8sclient, noResyncPeriodFunc())
if err := ksInformers.Iam().V1alpha2().Users().Informer().GetIndexer().Add(f.user); err != nil {
f.t.Errorf("add user:%s", err)
}
if err := ksInformers.Iam().V1alpha2().LoginRecords().Informer().GetIndexer().Add(f.loginRecord); err != nil {
f.t.Errorf("add login record:%s", err)
}
c := NewLoginRecordController(f.k8sclient, f.ksclient,
ksInformers.Iam().V1alpha2().LoginRecords(),
ksInformers.Iam().V1alpha2().Users(),
time.Minute*5)
c.userSynced = alwaysReady
c.loginRecordSynced = alwaysReady
c.recorder = &record.FakeRecorder{}
return c, ksInformers, k8sInformers
}
func (f *fixture) run(userName string) {
f.runController(userName, true, false)
}
func (f *fixture) runExpectError(userName string) {
f.runController(userName, true, true)
}
func (f *fixture) runController(user string, startInformers bool, expectError bool) {
c, i, k8sI := f.newController()
if startInformers {
stopCh := make(chan struct{})
defer close(stopCh)
i.Start(stopCh)
k8sI.Start(stopCh)
}
err := c.reconcile(user)
if !expectError && err != nil {
f.t.Errorf("error syncing user: %v", err)
} else if expectError && err == nil {
f.t.Error("expected error syncing user, got nil")
}
actions := filterInformerActions(f.ksclient.Actions())
for j, action := range actions {
if len(f.actions) < j+1 {
f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[j:])
break
}
expectedAction := f.actions[j]
checkAction(expectedAction, action, f.t)
}
if len(f.actions) > len(actions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
}
k8sActions := filterInformerActions(f.k8sclient.Actions())
for k, action := range k8sActions {
if len(f.kubeactions) < k+1 {
f.t.Errorf("%d unexpected actions: %+v", len(k8sActions)-len(f.kubeactions), k8sActions[k:])
break
}
expectedAction := f.kubeactions[k]
checkAction(expectedAction, action, f.t)
}
if len(f.kubeactions) > len(k8sActions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):])
}
}
// checkAction verifies that expected and actual actions are equal and both have
// same attached resources
func checkAction(expected, actual core.Action, t *testing.T) {
if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
return
}
if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
return
}
switch a := actual.(type) {
case core.CreateActionImpl:
e, _ := expected.(core.CreateActionImpl)
expObject := e.GetObject()
object := a.GetObject()
if !reflect.DeepEqual(expObject, object) {
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
}
case core.UpdateActionImpl:
e, _ := expected.(core.UpdateActionImpl)
expObject := e.GetObject()
object := a.GetObject()
expUser := expObject.(*iamv1alpha2.User)
user := object.(*iamv1alpha2.User)
expUser.Status.LastTransitionTime = nil
user.Status.LastTransitionTime = nil
if !reflect.DeepEqual(expUser, user) {
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expObject, object))
}
case core.PatchActionImpl:
e, _ := expected.(core.PatchActionImpl)
expPatch := e.GetPatch()
patch := a.GetPatch()
if !reflect.DeepEqual(expPatch, patch) {
t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintSideBySide(expPatch, patch))
}
default:
t.Errorf("Uncaptured Action %s %s, you should explicitly add a case to capture it",
actual.GetVerb(), actual.GetResource().Resource)
}
}
// filterInformerActions filters list and watch actions for testing resources.
// Since list and watch don't change resource state we can filter it to lower
// nose level in our tests.
func filterInformerActions(actions []core.Action) []core.Action {
var ret []core.Action
for _, action := range actions {
ret = append(ret, action)
}
return ret
}
func (f *fixture) expectUpdateUserStatusAction(user *iamv1alpha2.User) {
expect := user.DeepCopy()
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
action.Subresource = "status"
expect.Status.LastLoginTime = &f.loginRecord.CreationTimestamp
f.actions = append(f.actions, action)
}
func getKey(user *iamv1alpha2.User, t *testing.T) string {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(user)
if err != nil {
t.Errorf("Unexpected error getting key for user %v: %v", user.Name, err)
return ""
}
return key
}
func TestDoNothing(t *testing.T) {
f := newFixture(t)
user := newUser("test")
loginRecord := newLoginRecord("test")
f.user = user
f.loginRecord = loginRecord
f.objects = append(f.objects, user, loginRecord)
f.expectUpdateUserStatusAction(user)
f.run(getKey(user, t))
}
/*
*
* 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 user
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
iamv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
"time"
)
type LoginRecordController struct {
k8sClient kubernetes.Interface
ksClient kubesphere.Interface
loginRecordInformer iamv1alpha2informers.LoginRecordInformer
loginRecordLister iamv1alpha2listers.LoginRecordLister
loginRecordSynced cache.InformerSynced
// workqueue is a rate limited work queue. This is used to queue work to be
// processed instead of performing it as soon as a change happens. This
// means we can ensure we only process a fixed amount of resources at a
// time, and makes it easy to ensure we are never processing the same item
// simultaneously in two different workers.
workqueue workqueue.RateLimitingInterface
// recorder is an event recorder for recording Event resources to the
// Kubernetes API.
recorder record.EventRecorder
loginHistoryRetentionPeriod time.Duration
}
func NewLoginRecordController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface,
loginRecordInformer iamv1alpha2informers.LoginRecordInformer,
loginHistoryRetentionPeriod time.Duration) *LoginRecordController {
klog.V(4).Info("Creating event broadcaster")
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(klog.Infof)
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: k8sClient.CoreV1().Events("")})
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "loginrecord-controller"})
ctl := &LoginRecordController{
k8sClient: k8sClient,
ksClient: ksClient,
loginRecordInformer: loginRecordInformer,
loginRecordLister: loginRecordInformer.Lister(),
loginRecordSynced: loginRecordInformer.Informer().HasSynced,
loginHistoryRetentionPeriod: loginHistoryRetentionPeriod,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "loginrecord"),
recorder: recorder,
}
return ctl
}
func (c *LoginRecordController) Run(threadiness int, stopCh <-chan struct{}) error {
defer utilruntime.HandleCrash()
defer c.workqueue.ShutDown()
// Start the informer factories to begin populating the informer caches
klog.Info("Starting LoginRecord controller")
// Wait for the caches to be synced before starting workers
klog.Info("Waiting for informer caches to sync")
if ok := cache.WaitForCacheSync(stopCh, c.loginRecordSynced); !ok {
return fmt.Errorf("failed to wait for caches to sync")
}
klog.Info("Starting workers")
// Launch two workers to process Foo resources
for i := 0; i < threadiness; i++ {
go wait.Until(c.runWorker, time.Second, stopCh)
}
go wait.Until(func() {
if err := c.sync(); err != nil {
klog.Errorf("Error periodically sync user status, %v", err)
}
}, time.Hour, stopCh)
klog.Info("Started workers")
<-stopCh
klog.Info("Shutting down workers")
return nil
}
func (c *LoginRecordController) enqueueLoginRecord(obj interface{}) {
var key string
var err error
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
utilruntime.HandleError(err)
return
}
c.workqueue.Add(key)
}
func (c *LoginRecordController) runWorker() {
for c.processNextWorkItem() {
}
}
func (c *LoginRecordController) processNextWorkItem() bool {
obj, shutdown := c.workqueue.Get()
if shutdown {
return false
}
// We wrap this block in a func so we can defer c.workqueue.Done.
err := func(obj interface{}) error {
// We call Done here so the workqueue knows we have finished
// processing this item. We also must remember to call Forget if we
// do not want this work item being re-queued. For example, we do
// not call Forget if a transient error occurs, instead the item is
// put back on the workqueue and attempted again after a back-off
// period.
defer c.workqueue.Done(obj)
var key string
var ok bool
// We expect strings to come off the workqueue. These are of the
// form namespace/name. We do this as the delayed nature of the
// workqueue means the items in the informer cache may actually be
// more up to date that when the item was initially put onto the
// workqueue.
if key, ok = obj.(string); !ok {
// As the item in the workqueue is actually invalid, we call
// Forget here else we'd go into a loop of attempting to
// process a work item that is invalid.
c.workqueue.Forget(obj)
utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
return nil
}
// Run the reconcile, passing it the namespace/name string of the
// Foo resource to be synced.
if err := c.reconcile(key); err != nil {
// Put the item back on the workqueue to handle any transient errors.
c.workqueue.AddRateLimited(key)
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
}
// Finally, if no error occurs we Forget this item so it does not
// get queued again until another change happens.
c.workqueue.Forget(obj)
klog.Infof("Successfully synced %s:%s", "key", key)
return nil
}(obj)
if err != nil {
utilruntime.HandleError(err)
return true
}
return true
}
func (c *LoginRecordController) reconcile(key string) error {
loginRecord, err := c.loginRecordLister.Get(key)
if err != nil {
if errors.IsNotFound(err) {
utilruntime.HandleError(fmt.Errorf("login record '%s' in work queue no longer exists", key))
return nil
}
klog.Error(err)
return err
}
now := time.Now()
if loginRecord.CreationTimestamp.Add(c.loginHistoryRetentionPeriod).Before(now) { // login record beyonds retention period
if err = c.ksClient.IamV1alpha2().LoginRecords().Delete(loginRecord.Name, metav1.NewDeleteOptions(0)); err != nil {
klog.Error(err)
return err
}
} else { // put item back into the queue
c.workqueue.AddAfter(key, loginRecord.CreationTimestamp.Add(c.loginHistoryRetentionPeriod).Sub(now))
}
c.recorder.Event(loginRecord, corev1.EventTypeNormal, successSynced, messageResourceSynced)
return nil
}
func (c *LoginRecordController) Start(stopCh <-chan struct{}) error {
return c.Run(4, stopCh)
}
func (c *LoginRecordController) sync() error {
records, err := c.loginRecordLister.List(labels.Everything())
if err != nil {
return err
}
for _, record := range records {
key, err := cache.MetaNamespaceKeyFunc(record)
if err != nil {
return err
}
c.workqueue.AddRateLimited(key)
}
return nil
}
......@@ -80,32 +80,32 @@ func newUser(name string) *iamv1alpha2.User {
}
}
func (f *fixture) newController() (*Controller, ksinformers.SharedInformerFactory, kubeinformers.SharedInformerFactory) {
func (f *fixture) newController() (*userController, ksinformers.SharedInformerFactory, kubeinformers.SharedInformerFactory) {
f.ksclient = fake.NewSimpleClientset(f.objects...)
f.k8sclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
ldapClient := ldapclient.NewSimpleLdap()
ksinformers := ksinformers.NewSharedInformerFactory(f.ksclient, noResyncPeriodFunc())
k8sinformers := kubeinformers.NewSharedInformerFactory(f.k8sclient, noResyncPeriodFunc())
ksInformers := ksinformers.NewSharedInformerFactory(f.ksclient, noResyncPeriodFunc())
k8sInformers := kubeinformers.NewSharedInformerFactory(f.k8sclient, noResyncPeriodFunc())
for _, user := range f.userLister {
err := ksinformers.Iam().V1alpha2().Users().Informer().GetIndexer().Add(user)
err := ksInformers.Iam().V1alpha2().Users().Informer().GetIndexer().Add(user)
if err != nil {
f.t.Errorf("add user:%s", err)
}
}
c := NewUserController(f.k8sclient, f.ksclient, nil,
ksinformers.Iam().V1alpha2().Users(),
ksInformers.Iam().V1alpha2().Users(),
ksInformers.Iam().V1alpha2().LoginRecords(),
nil, nil,
ksinformers.Iam().V1alpha2().LoginRecords(),
k8sinformers.Core().V1().ConfigMaps(),
k8sInformers.Core().V1().ConfigMaps(),
ldapClient, nil,
options.NewAuthenticateOptions(), false)
c.userSynced = alwaysReady
c.Synced = []cache.InformerSynced{alwaysReady}
c.recorder = &record.FakeRecorder{}
return c, ksinformers, k8sinformers
return c, ksInformers, k8sInformers
}
func (f *fixture) run(userName string) {
......@@ -230,14 +230,14 @@ func filterInformerActions(actions []core.Action) []core.Action {
func (f *fixture) expectUpdateUserStatusAction(user *iamv1alpha2.User) {
expect := user.DeepCopy()
//expect.Status.State = iamv1alpha2.UserActive
expect.Finalizers = []string{"finalizers.kubesphere.io/users"}
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
f.actions = append(f.actions, action)
expect = expect.DeepCopy()
expect.Status.State = iamv1alpha2.UserActive
expect.Annotations = map[string]string{iamv1alpha2.PasswordEncryptedAnnotation: "true"}
action = core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
action.Subresource = "status"
f.actions = append(f.actions, action)
}
......
......@@ -39,10 +39,7 @@ func (a *EmailValidator) Handle(ctx context.Context, req admission.Request) admi
}
allUsers := v1alpha2.UserList{}
err = a.Client.List(ctx, &allUsers, &client.ListOptions{})
if err != nil {
if err = a.Client.List(ctx, &allUsers, &client.ListOptions{}); err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
......@@ -51,7 +48,6 @@ func (a *EmailValidator) Handle(ctx context.Context, req admission.Request) admi
}
alreadyExist := emailAlreadyExist(allUsers, user)
if alreadyExist {
return admission.Errored(http.StatusConflict, fmt.Errorf("user email: %s already exists", user.Spec.Email))
}
......
此差异已折叠。
......@@ -17,6 +17,7 @@ limitations under the License.
package v1alpha2
import (
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"net/http"
"github.com/emicklei/go-restful"
......@@ -25,9 +26,7 @@ import (
v1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/iam"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/iam/am"
......@@ -42,9 +41,9 @@ const (
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"}
func AddToContainer(container *restful.Container, im im.IdentityManagementInterface, am am.AccessManagementInterface, group group.GroupOperator, options *authoptions.AuthenticationOptions) error {
func AddToContainer(container *restful.Container, im im.IdentityManagementInterface, am am.AccessManagementInterface, group group.GroupOperator, authorizer authorizer.Authorizer) error {
ws := runtime.NewWebService(GroupVersion)
handler := newIAMHandler(im, am, group, options)
handler := newIAMHandler(im, am, group, authorizer)
// users
ws.Route(ws.POST("/users").
......@@ -69,7 +68,7 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf
ws.Route(ws.PUT("/users/{user}/password").
To(handler.ModifyPassword).
Doc("Reset password of the specified user.").
Reads(iam.PasswordReset{}).
Reads(PasswordReset{}).
Param(ws.PathParameter("user", "username")).
Returns(http.StatusOK, api.StatusOK, errors.None).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.UserTag}))
......
......@@ -20,62 +20,101 @@ import (
"fmt"
"github.com/emicklei/go-restful"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/auth"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/models/auth"
"kubesphere.io/kubesphere/pkg/models/iam/im"
"net/http"
)
const (
KindTokenReview = "TokenReview"
passwordGrantType = "password"
refreshTokenGrantType = "refresh_token"
)
type Spec struct {
Token string `json:"token" description:"access token"`
}
type Status struct {
Authenticated bool `json:"authenticated" description:"is authenticated"`
User map[string]interface{} `json:"user,omitempty" description:"user info"`
}
type TokenReview struct {
APIVersion string `json:"apiVersion" description:"Kubernetes API version"`
Kind string `json:"kind" description:"kind of the API object"`
Spec *Spec `json:"spec,omitempty"`
Status *Status `json:"status,omitempty" description:"token review status"`
}
type LoginRequest struct {
Username string `json:"username" description:"username"`
Password string `json:"password" description:"password"`
}
func (request *TokenReview) Validate() error {
if request.Spec == nil || request.Spec.Token == "" {
return fmt.Errorf("token must not be null")
}
return nil
}
type handler struct {
im im.IdentityManagementInterface
options *authoptions.AuthenticationOptions
tokenOperator im.TokenManagementInterface
authenticator im.PasswordAuthenticator
loginRecorder im.LoginRecorder
im im.IdentityManagementInterface
options *authoptions.AuthenticationOptions
tokenOperator auth.TokenManagementInterface
passwordAuthenticator auth.PasswordAuthenticator
oauth2Authenticator auth.OAuth2Authenticator
loginRecorder auth.LoginRecorder
}
func newHandler(im im.IdentityManagementInterface, tokenOperator im.TokenManagementInterface, authenticator im.PasswordAuthenticator, loginRecorder im.LoginRecorder, options *authoptions.AuthenticationOptions) *handler {
return &handler{im: im, tokenOperator: tokenOperator, authenticator: authenticator, loginRecorder: loginRecorder, options: options}
func newHandler(im im.IdentityManagementInterface,
tokenOperator auth.TokenManagementInterface,
passwordAuthenticator auth.PasswordAuthenticator,
oauth2Authenticator auth.OAuth2Authenticator,
loginRecorder auth.LoginRecorder,
options *authoptions.AuthenticationOptions) *handler {
return &handler{im: im,
tokenOperator: tokenOperator,
passwordAuthenticator: passwordAuthenticator,
oauth2Authenticator: oauth2Authenticator,
loginRecorder: loginRecorder,
options: options}
}
// Implement webhook authentication interface
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
func (h *handler) TokenReview(req *restful.Request, resp *restful.Response) {
var tokenReview auth.TokenReview
var tokenReview TokenReview
err := req.ReadEntity(&tokenReview)
if err != nil {
klog.Error(err)
api.HandleBadRequest(resp, req, err)
return
}
if err = tokenReview.Validate(); err != nil {
klog.Error(err)
api.HandleBadRequest(resp, req, err)
return
}
authenticated, err := h.tokenOperator.Verify(tokenReview.Spec.Token)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, req, err)
return
}
success := auth.TokenReview{APIVersion: tokenReview.APIVersion,
Kind: auth.KindTokenReview,
Status: &auth.Status{
success := TokenReview{APIVersion: tokenReview.APIVersion,
Kind: KindTokenReview,
Status: &Status{
Authenticated: true,
User: map[string]interface{}{"username": authenticated.GetName(), "uid": authenticated.GetUID()},
},
......@@ -93,34 +132,33 @@ func (h *handler) Authorize(req *restful.Request, resp *restful.Response) {
conf, err := h.options.OAuthOptions.OAuthClient(clientId)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
api.HandleError(resp, req, err)
return
}
if responseType != "token" {
err := apierrors.NewBadRequest(fmt.Sprintf("Response type %s is not supported", responseType))
resp.WriteError(http.StatusUnauthorized, err)
api.HandleError(resp, req, err)
return
}
if !ok {
err := apierrors.NewUnauthorized("Unauthorized")
resp.WriteError(http.StatusUnauthorized, err)
api.HandleError(resp, req, err)
return
}
token, err := h.tokenOperator.IssueTo(authenticated)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
api.HandleError(resp, req, err)
return
}
redirectURL, err := conf.ResolveRedirectURL(redirectURI)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
api.HandleError(resp, req, err)
return
}
......@@ -133,105 +171,41 @@ func (h *handler) Authorize(req *restful.Request, resp *restful.Response) {
http.Redirect(resp, req.Request, redirectURL, http.StatusFound)
}
func (h *handler) oAuthCallBack(req *restful.Request, resp *restful.Response) {
func (h *handler) oauthCallBack(req *restful.Request, resp *restful.Response) {
code := req.QueryParameter("code")
name := req.PathParameter("callback")
provider := req.PathParameter("callback")
if code == "" {
err := apierrors.NewUnauthorized("Unauthorized: missing code")
resp.WriteError(http.StatusUnauthorized, err)
}
providerOptions, err := h.options.OAuthOptions.IdentityProviderOptions(name)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
}
oauthIdentityProvider, err := identityprovider.GetOAuthProvider(providerOptions.Type, providerOptions.Provider)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
api.HandleError(resp, req, err)
return
}
identity, err := oauthIdentityProvider.IdentityExchange(code)
authenticated, provider, err := h.oauth2Authenticator.Authenticate(provider, code)
if err != nil {
err = apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
api.HandleUnauthorized(resp, req, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
return
}
authenticated, err := h.im.DescribeUser(identity.GetName())
if err != nil {
// create user if not exist
if (oauth.MappingMethodAuto == providerOptions.MappingMethod ||
oauth.MappingMethodMixed == providerOptions.MappingMethod) &&
apierrors.IsNotFound(err) {
create := &iamv1alpha2.User{
ObjectMeta: v1.ObjectMeta{Name: identity.GetName(),
Annotations: map[string]string{iamv1alpha2.IdentifyProviderLabel: providerOptions.Name}},
Spec: iamv1alpha2.UserSpec{Email: identity.GetEmail()},
}
if authenticated, err = h.im.CreateUser(create); err != nil {
klog.Error(err)
api.HandleInternalError(resp, req, err)
return
}
} else {
klog.Error(err)
api.HandleInternalError(resp, req, err)
return
}
}
if oauth.MappingMethodLookup == providerOptions.MappingMethod &&
authenticated == nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("user %s cannot bound to this identify provider", identity.GetName()))
klog.Error(err)
resp.WriteError(http.StatusUnauthorized, err)
return
}
// oauth.MappingMethodAuto
// Fails if a user with that user name is already mapped to another identity.
if providerOptions.MappingMethod == oauth.MappingMethodAuto && authenticated.Annotations[iamv1alpha2.IdentifyProviderLabel] != providerOptions.Name {
err := apierrors.NewUnauthorized(fmt.Sprintf("user %s is already bound to other identify provider", identity.GetName()))
klog.Error(err)
resp.WriteError(http.StatusUnauthorized, err)
return
}
result, err := h.tokenOperator.IssueTo(&user.DefaultInfo{
Name: authenticated.Name,
UID: string(authenticated.UID),
})
result, err := h.tokenOperator.IssueTo(authenticated)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err)
api.HandleInternalError(resp, req, apierrors.NewInternalError(err))
return
}
if err = h.loginRecorder.RecordLogin(authenticated.Name, iamv1alpha2.OAuth, providerOptions.Name, nil, req.Request); err != nil {
klog.Error(err)
err := apierrors.NewInternalError(err)
resp.WriteError(http.StatusInternalServerError, err)
return
requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, nil); err != nil {
klog.Errorf("Failed to record successful login for user %s, error: %v", authenticated.GetName(), err)
}
resp.WriteEntity(result)
}
func (h *handler) Login(request *restful.Request, response *restful.Response) {
var loginRequest auth.LoginRequest
var loginRequest LoginRequest
err := request.ReadEntity(&loginRequest)
if err != nil || loginRequest.Username == "" || loginRequest.Password == "" {
response.WriteHeaderAndEntity(http.StatusUnauthorized, fmt.Errorf("empty username or password"))
if err != nil {
api.HandleBadRequest(response, request, err)
return
}
h.passwordGrant(loginRequest.Username, loginRequest.Password, request, response)
......@@ -240,71 +214,53 @@ func (h *handler) Login(request *restful.Request, response *restful.Response) {
func (h *handler) Token(req *restful.Request, response *restful.Response) {
grantType, err := req.BodyParameter("grant_type")
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, req, err)
return
}
switch grantType {
case "password":
username, err := req.BodyParameter("username")
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, req, err)
return
}
password, err := req.BodyParameter("password")
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, req, err)
return
}
case passwordGrantType:
username, _ := req.BodyParameter("username")
password, _ := req.BodyParameter("password")
h.passwordGrant(username, password, req, response)
break
case "refresh_token":
case refreshTokenGrantType:
h.refreshTokenGrant(req, response)
break
default:
err := apierrors.NewBadRequest(fmt.Sprintf("Grant type %s is not supported", grantType))
response.WriteError(http.StatusBadRequest, err)
api.HandleBadRequest(response, req, err)
}
}
func (h *handler) passwordGrant(username string, password string, req *restful.Request, response *restful.Response) {
authenticated, err := h.authenticator.Authenticate(username, password)
authenticated, provider, err := h.passwordAuthenticator.Authenticate(username, password)
if err != nil {
klog.Error(err)
switch err {
case im.AuthFailedIncorrectPassword:
if err := h.loginRecorder.RecordLogin(username, iamv1alpha2.Token, "", err, req.Request); err != nil {
klog.Error(err)
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
return
case auth.IncorrectPasswordError:
requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
if err := h.loginRecorder.RecordLogin(username, iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, err); err != nil {
klog.Errorf("Failed to record unsuccessful login attempt for user %s, error: %v", username, err)
}
response.WriteError(http.StatusUnauthorized, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
return
case im.AuthFailedIdentityMappingNotMatch:
response.WriteError(http.StatusUnauthorized, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
return
case im.AuthRateLimitExceeded:
response.WriteError(http.StatusTooManyRequests, apierrors.NewTooManyRequests(fmt.Sprintf("Unauthorized: %s", err), 60))
case auth.RateLimitExceededError:
api.HandleTooManyRequests(response, req, apierrors.NewTooManyRequestsError(fmt.Sprintf("Unauthorized: %s", err)))
return
default:
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
api.HandleInternalError(response, req, apierrors.NewInternalError(err))
return
}
}
result, err := h.tokenOperator.IssueTo(authenticated)
if err != nil {
klog.Error(err)
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
api.HandleInternalError(response, req, apierrors.NewInternalError(err))
return
}
if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, "", nil, req.Request); err != nil {
klog.Error(err)
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
return
requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, nil); err != nil {
klog.Errorf("Failed to record successful login for user %s, error: %v", username, err)
}
response.WriteEntity(result)
......@@ -313,22 +269,46 @@ func (h *handler) passwordGrant(username string, password string, req *restful.R
func (h *handler) refreshTokenGrant(req *restful.Request, response *restful.Response) {
refreshToken, err := req.BodyParameter("refresh_token")
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, req, err)
api.HandleBadRequest(response, req, apierrors.NewBadRequest(err.Error()))
return
}
authenticated, err := h.tokenOperator.Verify(refreshToken)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
response.WriteError(http.StatusUnauthorized, err)
api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
return
}
// update token after registration
if authenticated.GetName() == iamv1alpha2.PreRegistrationUser &&
authenticated.GetExtra() != nil &&
len(authenticated.GetExtra()[iamv1alpha2.ExtraIdentityProvider]) > 0 &&
len(authenticated.GetExtra()[iamv1alpha2.ExtraUID]) > 0 {
idp := authenticated.GetExtra()[iamv1alpha2.ExtraIdentityProvider][0]
uid := authenticated.GetExtra()[iamv1alpha2.ExtraUID][0]
queryParam := query.New()
queryParam.LabelSelector = labels.SelectorFromSet(labels.Set{
iamv1alpha2.IdentifyProviderLabel: idp,
iamv1alpha2.OriginUIDLabel: uid}).String()
result, err := h.im.ListUsers(queryParam)
if err != nil {
api.HandleInternalError(response, req, apierrors.NewInternalError(err))
return
}
if len(result.Items) != 1 {
err := apierrors.NewUnauthorized("authenticated user does not exist")
api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
return
}
authenticated = &user.DefaultInfo{Name: result.Items[0].(*iamv1alpha2.User).Name}
}
result, err := h.tokenOperator.IssueTo(authenticated)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
response.WriteError(http.StatusUnauthorized, err)
api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
return
}
......
......@@ -20,10 +20,10 @@ import (
"github.com/emicklei/go-restful"
restfulspec "github.com/emicklei/go-restful-openapi"
"kubesphere.io/kubesphere/pkg/api"
"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/constants"
"kubesphere.io/kubesphere/pkg/models/auth"
"kubesphere.io/kubesphere/pkg/models/iam/im"
"net/http"
)
......@@ -34,22 +34,28 @@ import (
// Most authentication integrations place an authenticating proxy in front of this endpoint, or configure ks-apiserver
// to validate credentials against a backing identity provider.
// Requests to <ks-apiserver>/oauth/authorize can come from user-agents that cannot display interactive login pages, such as the CLI.
func AddToContainer(c *restful.Container, im im.IdentityManagementInterface, tokenOperator im.TokenManagementInterface, authenticator im.PasswordAuthenticator, loginRecorder im.LoginRecorder, options *authoptions.AuthenticationOptions) error {
func AddToContainer(c *restful.Container, im im.IdentityManagementInterface,
tokenOperator auth.TokenManagementInterface,
passwordAuthenticator auth.PasswordAuthenticator,
oauth2Authenticator auth.OAuth2Authenticator,
loginRecorder auth.LoginRecorder,
options *authoptions.AuthenticationOptions) error {
ws := &restful.WebService{}
ws.Path("/oauth").
Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON)
handler := newHandler(im, tokenOperator, authenticator, loginRecorder, options)
handler := newHandler(im, tokenOperator, passwordAuthenticator, oauth2Authenticator, loginRecorder, options)
// Implement webhook authentication interface
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
ws.Route(ws.POST("/authenticate").
Doc("TokenReview attempts to authenticate a token to a known user. Note: TokenReview requests may be "+
"cached by the webhook token authenticator plugin in the kube-apiserver.").
Reads(auth.TokenReview{}).
Reads(TokenReview{}).
To(handler.TokenReview).
Returns(http.StatusOK, api.StatusOK, auth.TokenReview{}).
Returns(http.StatusOK, api.StatusOK, TokenReview{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))
// Only support implicit grant flow
......@@ -98,7 +104,7 @@ func AddToContainer(c *restful.Container, im im.IdentityManagementInterface, tok
"otherwise, REQUIRED. The scope of the access token as described by [RFC6479] Section 3.3.").Required(false)).
Param(ws.QueryParameter("state", "if the \"state\" parameter was present in the client authorization request."+
"The exact value received from the client.").Required(true)).
To(handler.oAuthCallBack).
To(handler.oauthCallBack).
Returns(http.StatusOK, api.StatusOK, oauth.Token{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))
......@@ -113,7 +119,7 @@ func AddToContainer(c *restful.Container, im im.IdentityManagementInterface, tok
To(handler.Login).
Deprecate().
Doc("KubeSphere APIs support token-based authentication via the Authtoken request header. The POST Login API is used to retrieve the authentication token. After the authentication token is obtained, it must be inserted into the Authtoken header for all requests.").
Reads(auth.LoginRequest{}).
Reads(LoginRequest{}).
Returns(http.StatusOK, api.StatusOK, oauth.Token{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))
......
......@@ -29,7 +29,10 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/query"
fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/components"
resourcev1alpha2 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
fakeapp "sigs.k8s.io/application/pkg/client/clientset/versioned/fake"
"testing"
)
......@@ -88,7 +91,9 @@ func TestResourceV1alpha2Fallback(t *testing.T) {
t.Fatal(err)
}
handler := New(factory)
handler := New(resourcev1alpha3.NewResourceGetter(factory),
resourcev1alpha2.NewResourceGetter(factory),
components.NewComponentsGetter(factory.KubernetesSharedInformerFactory()))
for _, test := range tests {
got, err := listResources(test.namespace, test.resource, test.query, handler)
......
......@@ -25,6 +25,9 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/components"
resourcev1alpha2 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
"net/http"
)
......@@ -47,7 +50,9 @@ func Resource(resource string) schema.GroupResource {
func AddToContainer(c *restful.Container, informerFactory informers.InformerFactory) error {
webservice := runtime.NewWebService(GroupVersion)
handler := New(informerFactory)
handler := New(resourcev1alpha3.NewResourceGetter(informerFactory),
resourcev1alpha2.NewResourceGetter(informerFactory),
components.NewComponentsGetter(informerFactory.KubernetesSharedInformerFactory()))
webservice.Route(webservice.GET("/{resources}").
To(handler.handleListResources).
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册