improve identity provider plugin

Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2020-11-23 15:04:59 +08:00
parent 91c2e05616
commit dfaefa5ffb
63 changed files with 3656 additions and 1746 deletions

View File

@@ -3775,7 +3775,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/auth.LoginRequest" "$ref": "#/definitions/oauth.LoginRequest"
} }
} }
], ],
@@ -4524,7 +4524,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/iam.PasswordReset" "$ref": "#/definitions/v1alpha2.PasswordReset"
} }
}, },
{ {
@@ -12627,7 +12627,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/auth.TokenReview" "$ref": "#/definitions/oauth.TokenReview"
} }
} }
], ],
@@ -12635,7 +12635,7 @@
"200": { "200": {
"description": "ok", "description": "ok",
"schema": { "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": { "big.Int": {
"required": [ "required": [
"neg", "neg",
@@ -15275,20 +15210,6 @@
} }
} }
}, },
"iam.PasswordReset": {
"required": [
"currentPassword",
"password"
],
"properties": {
"currentPassword": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"inf.Dec": { "inf.Dec": {
"required": [ "required": [
"unscaled", "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": { "oauth.Token": {
"required": [ "required": [
"access_token" "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": { "openpitrix.App": {
"required": [ "required": [
"category_set" "category_set"
@@ -21672,6 +21658,20 @@
} }
} }
}, },
"v1alpha2.PasswordReset": {
"required": [
"currentPassword",
"password"
],
"properties": {
"currentPassword": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"v1alpha2.Row": { "v1alpha2.Row": {
"required": [ "required": [
"id", "id",

View File

@@ -34,6 +34,7 @@ import (
"kubesphere.io/kubesphere/pkg/controller/group" "kubesphere.io/kubesphere/pkg/controller/group"
"kubesphere.io/kubesphere/pkg/controller/groupbinding" "kubesphere.io/kubesphere/pkg/controller/groupbinding"
"kubesphere.io/kubesphere/pkg/controller/job" "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/ippool"
"kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy" "kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy"
"kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy/provider" "kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy/provider"
@@ -208,19 +209,19 @@ func addControllers(
go fedWorkspaceRoleBindingCacheController.Run(stopCh) go fedWorkspaceRoleBindingCacheController.Run(stopCh)
} }
userController := user.NewUserController(client.Kubernetes(), client.KubeSphere(), userController := user.NewUserController(client.Kubernetes(), client.KubeSphere(), client.Config(),
client.Config(),
kubesphereInformer.Iam().V1alpha2().Users(), kubesphereInformer.Iam().V1alpha2().Users(),
fedUserCache, fedUserCacheController,
kubesphereInformer.Iam().V1alpha2().LoginRecords(), kubesphereInformer.Iam().V1alpha2().LoginRecords(),
fedUserCache, fedUserCacheController,
kubernetesInformer.Core().V1().ConfigMaps(), kubernetesInformer.Core().V1().ConfigMaps(),
ldapClient, devopsClient, ldapClient, devopsClient,
authenticationOptions, multiClusterEnabled) authenticationOptions, multiClusterEnabled)
loginRecordController := user.NewLoginRecordController( loginRecordController := loginrecord.NewLoginRecordController(
client.Kubernetes(), client.Kubernetes(),
client.KubeSphere(), client.KubeSphere(),
kubesphereInformer.Iam().V1alpha2().LoginRecords(), kubesphereInformer.Iam().V1alpha2().LoginRecords(),
kubesphereInformer.Iam().V1alpha2().Users(),
authenticationOptions.LoginHistoryRetentionPeriod) authenticationOptions.LoginHistoryRetentionPeriod)
csrController := certificatesigningrequest.NewController(client.Kubernetes(), csrController := certificatesigningrequest.NewController(client.Kubernetes(),

View File

@@ -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' internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string type: string
groupRef: groupRef:
description: Ref defines the desired relation of GroupBinding description: GroupRef defines the desired relation of GroupBinding
properties: properties:
apiGroup: apiGroup:
type: string type: string

View File

@@ -24,7 +24,8 @@ spec:
plural: users plural: users
singular: user singular: user
scope: Cluster scope: Cluster
subresources: {} subresources:
status: {}
validation: validation:
openAPIV3Schema: openAPIV3Schema:
description: User is the Schema for the users API description: User is the Schema for the users API

137
go.mod
View File

@@ -38,16 +38,16 @@ require (
github.com/gocraft/dbr v0.0.0-20180507214907-a0fd650918f6 github.com/gocraft/dbr v0.0.0-20180507214907-a0fd650918f6
github.com/golang/example v0.0.0-20170904185048-46695d81d1fa github.com/golang/example v0.0.0-20170904185048-46695d81d1fa
github.com/golang/mock v1.2.0 github.com/golang/mock v1.2.0
github.com/golang/protobuf v1.4.3 github.com/golang/protobuf v1.4.0
github.com/google/go-cmp v0.4.0 github.com/google/go-cmp v0.4.0
github.com/google/go-querystring v1.0.0 // indirect github.com/google/go-querystring v1.0.0 // indirect
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.1 // indirect github.com/gorilla/mux v1.7.1 // indirect
github.com/gorilla/websocket v1.4.1 github.com/gorilla/websocket v1.4.1
github.com/hashicorp/go-version v1.2.0 // indirect github.com/hashicorp/go-version v1.2.0 // indirect
github.com/json-iterator/go v1.1.10 github.com/json-iterator/go v1.1.9
github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/kiali/kiali v1.26.0 github.com/kiali/kiali v0.15.1-0.20201110082537-0c2b977257d4
github.com/kubernetes-csi/external-snapshotter/v2 v2.1.0 github.com/kubernetes-csi/external-snapshotter/v2 v2.1.0
github.com/kubesphere/sonargo v0.0.2 github.com/kubesphere/sonargo v0.0.2
github.com/lib/pq v1.2.0 // indirect github.com/lib/pq v1.2.0 // indirect
@@ -62,9 +62,9 @@ require (
github.com/projectcalico/kube-controllers v3.8.8+incompatible github.com/projectcalico/kube-controllers v3.8.8+incompatible
github.com/projectcalico/libcalico-go v1.7.2-0.20191104213956-8f81e1e344ce github.com/projectcalico/libcalico-go v1.7.2-0.20191104213956-8f81e1e344ce
github.com/prometheus-community/prom-label-proxy v0.2.0 github.com/prometheus-community/prom-label-proxy v0.2.0
github.com/prometheus/client_golang v1.8.0 github.com/prometheus/client_golang v1.5.1
github.com/prometheus/common v0.14.0 github.com/prometheus/common v0.9.1
github.com/prometheus/prometheus v2.5.0+incompatible github.com/prometheus/prometheus v1.8.2-0.20200507164740-ecee9c8abfd1
github.com/sony/sonyflake v1.0.0 github.com/sony/sonyflake v1.0.0
github.com/speps/go-hashids v2.0.0+incompatible github.com/speps/go-hashids v2.0.0+incompatible
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5
@@ -73,13 +73,13 @@ require (
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/xanzy/ssh-agent v0.2.1 // indirect github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20200422194213-44a606286825 golang.org/x/crypto v0.0.0-20200422194213-44a606286825
golang.org/x/net v0.0.0-20200625001655-4c5254603344 golang.org/x/net v0.0.0-20200421231249-e086a090c8fd
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
google.golang.org/grpc v1.29.0 google.golang.org/grpc v1.29.0
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect
gopkg.in/src-d/go-git.v4 v4.11.0 gopkg.in/src-d/go-git.v4 v4.11.0
gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
gotest.tools v2.2.0+incompatible gotest.tools v2.2.0+incompatible
istio.io/api v0.0.0-20191111210003-35e06ef8d838 istio.io/api v0.0.0-20191111210003-35e06ef8d838
@@ -107,15 +107,25 @@ require (
replace ( replace (
cloud.google.com/go => cloud.google.com/go v0.38.0 cloud.google.com/go => cloud.google.com/go v0.38.0
cloud.google.com/go/bigquery => cloud.google.com/go/bigquery v1.3.0
cloud.google.com/go/bigtable => cloud.google.com/go/bigtable v1.2.0
cloud.google.com/go/pubsub => cloud.google.com/go/pubsub v1.1.0
cloud.google.com/go/storage => cloud.google.com/go/storage v1.5.0
code.cloudfoundry.org/bytefmt => code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6 code.cloudfoundry.org/bytefmt => code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6
collectd.org => collectd.org v0.3.0
github.com/Azure/azure-sdk-for-go => github.com/Azure/azure-sdk-for-go v41.3.0+incompatible
github.com/Azure/go-ansiterm => github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 github.com/Azure/go-ansiterm => github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78
github.com/Azure/go-autorest/autorest => github.com/Azure/go-autorest/autorest v0.9.0 github.com/Azure/go-autorest/autorest => github.com/Azure/go-autorest/autorest v0.9.0
github.com/Azure/go-autorest/autorest/adal => github.com/Azure/go-autorest/autorest/adal v0.5.0 github.com/Azure/go-autorest/autorest/adal => github.com/Azure/go-autorest/autorest/adal v0.5.0
github.com/Azure/go-autorest/autorest/date => github.com/Azure/go-autorest/autorest/date v0.1.0 github.com/Azure/go-autorest/autorest/date => github.com/Azure/go-autorest/autorest/date v0.1.0
github.com/Azure/go-autorest/autorest/mocks => github.com/Azure/go-autorest/autorest/mocks v0.2.0 github.com/Azure/go-autorest/autorest/mocks => github.com/Azure/go-autorest/autorest/mocks v0.2.0
github.com/Azure/go-autorest/autorest/to => github.com/Azure/go-autorest/autorest/to v0.3.0
github.com/Azure/go-autorest/autorest/validation => github.com/Azure/go-autorest/autorest/validation v0.2.0
github.com/Azure/go-autorest/logger => github.com/Azure/go-autorest/logger v0.1.0 github.com/Azure/go-autorest/logger => github.com/Azure/go-autorest/logger v0.1.0
github.com/Azure/go-autorest/tracing => github.com/Azure/go-autorest/tracing v0.5.0 github.com/Azure/go-autorest/tracing => github.com/Azure/go-autorest/tracing v0.5.0
github.com/BurntSushi/toml => github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml => github.com/BurntSushi/toml v0.3.1
github.com/DATA-DOG/go-sqlmock => github.com/DATA-DOG/go-sqlmock v1.3.3
github.com/DataDog/datadog-go => github.com/DataDog/datadog-go v3.2.0+incompatible
github.com/MakeNowJust/heredoc => github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd github.com/MakeNowJust/heredoc => github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd
github.com/Masterminds/goutils => github.com/Masterminds/goutils v1.1.0 github.com/Masterminds/goutils => github.com/Masterminds/goutils v1.1.0
github.com/Masterminds/semver => github.com/Masterminds/semver v1.5.0 github.com/Masterminds/semver => github.com/Masterminds/semver v1.5.0
@@ -135,10 +145,14 @@ replace (
github.com/alcortesm/tgz => github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 github.com/alcortesm/tgz => github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7
github.com/alecthomas/template => github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc github.com/alecthomas/template => github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
github.com/alecthomas/units => github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf github.com/alecthomas/units => github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf
github.com/andreyvit/diff => github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
github.com/andybalholm/cascadia => github.com/andybalholm/cascadia v1.0.0 github.com/andybalholm/cascadia => github.com/andybalholm/cascadia v1.0.0
github.com/anmitsu/go-shlex => github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 github.com/anmitsu/go-shlex => github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239
github.com/appscode/jsonpatch => github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30 github.com/apache/arrow/go/arrow => github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db
github.com/armon/circbuf => github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e
github.com/armon/consul-api => github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 github.com/armon/consul-api => github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6
github.com/armon/go-metrics => github.com/armon/go-metrics v0.3.3
github.com/armon/go-radix => github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310
github.com/asaskevich/govalidator => github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a github.com/asaskevich/govalidator => github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
github.com/aws/aws-sdk-go => github.com/aws/aws-sdk-go v1.22.2 github.com/aws/aws-sdk-go => github.com/aws/aws-sdk-go v1.22.2
github.com/beevik/etree => github.com/beevik/etree v1.1.0 github.com/beevik/etree => github.com/beevik/etree v1.1.0
@@ -147,17 +161,25 @@ replace (
github.com/bitly/go-simplejson => github.com/bitly/go-simplejson v0.5.0 github.com/bitly/go-simplejson => github.com/bitly/go-simplejson v0.5.0
github.com/blang/semver => github.com/blang/semver v3.5.0+incompatible github.com/blang/semver => github.com/blang/semver v3.5.0+incompatible
github.com/bmizerany/assert => github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 github.com/bmizerany/assert => github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/bmizerany/pat => github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40
github.com/boltdb/bolt => github.com/boltdb/bolt v1.3.1
github.com/bshuster-repo/logrus-logstash-hook => github.com/bshuster-repo/logrus-logstash-hook v0.4.1 github.com/bshuster-repo/logrus-logstash-hook => github.com/bshuster-repo/logrus-logstash-hook v0.4.1
github.com/bugsnag/bugsnag-go => github.com/bugsnag/bugsnag-go v1.5.0 github.com/bugsnag/bugsnag-go => github.com/bugsnag/bugsnag-go v1.5.0
github.com/bugsnag/panicwrap => github.com/bugsnag/panicwrap v1.2.0 github.com/bugsnag/panicwrap => github.com/bugsnag/panicwrap v1.2.0
github.com/c-bata/go-prompt => github.com/c-bata/go-prompt v0.2.2
github.com/cenkalti/backoff => github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409
github.com/cespare/xxhash => github.com/cespare/xxhash v1.1.0 github.com/cespare/xxhash => github.com/cespare/xxhash v1.1.0
github.com/cespare/xxhash/v2 => github.com/cespare/xxhash/v2 v2.1.1
github.com/chai2010/gettext-go => github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 github.com/chai2010/gettext-go => github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5
github.com/chai2010/jsonmap => github.com/chai2010/jsonmap v1.0.0 github.com/chai2010/jsonmap => github.com/chai2010/jsonmap v1.0.0
github.com/circonus-labs/circonus-gometrics => github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible
github.com/circonus-labs/circonusllhist => github.com/circonus-labs/circonusllhist v0.1.3
github.com/client9/misspell => github.com/client9/misspell v0.3.4 github.com/client9/misspell => github.com/client9/misspell v0.3.4
github.com/cockroachdb/datadriven => github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa github.com/cockroachdb/datadriven => github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa
github.com/container-storage-interface/spec => github.com/container-storage-interface/spec v1.2.0 github.com/container-storage-interface/spec => github.com/container-storage-interface/spec v1.2.0
github.com/containerd/containerd => github.com/containerd/containerd v1.3.0 github.com/containerd/containerd => github.com/containerd/containerd v1.3.0
github.com/containerd/continuity => github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 github.com/containerd/continuity => github.com/containerd/continuity v0.0.0-20181203112020-004b46473808
github.com/containernetworking/cni => github.com/containernetworking/cni v0.8.0
github.com/coreos/bbolt => github.com/coreos/bbolt v1.3.3 github.com/coreos/bbolt => github.com/coreos/bbolt v1.3.3
github.com/coreos/etcd => github.com/coreos/etcd v3.3.17+incompatible github.com/coreos/etcd => github.com/coreos/etcd v3.3.17+incompatible
github.com/coreos/go-oidc => github.com/coreos/go-oidc v2.1.0+incompatible github.com/coreos/go-oidc => github.com/coreos/go-oidc v2.1.0+incompatible
@@ -167,12 +189,15 @@ replace (
github.com/cpuguy83/go-md2man => github.com/cpuguy83/go-md2man v1.0.10 github.com/cpuguy83/go-md2man => github.com/cpuguy83/go-md2man v1.0.10
github.com/creack/pty => github.com/creack/pty v1.1.7 github.com/creack/pty => github.com/creack/pty v1.1.7
github.com/cyphar/filepath-securejoin => github.com/cyphar/filepath-securejoin v0.2.2 github.com/cyphar/filepath-securejoin => github.com/cyphar/filepath-securejoin v0.2.2
github.com/dave/jennifer => github.com/dave/jennifer v1.2.0
github.com/davecgh/go-spew => github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew => github.com/davecgh/go-spew v1.1.1
github.com/daviddengcn/go-colortext => github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd github.com/daviddengcn/go-colortext => github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd
github.com/deckarep/golang-set => github.com/deckarep/golang-set v1.7.1 github.com/deckarep/golang-set => github.com/deckarep/golang-set v1.7.1
github.com/deislabs/oras => github.com/deislabs/oras v0.7.0 github.com/deislabs/oras => github.com/deislabs/oras v0.7.0
github.com/denisenkom/go-mssqldb => github.com/denisenkom/go-mssqldb v0.0.0-20190204142019-df6d76eb9289 github.com/denisenkom/go-mssqldb => github.com/denisenkom/go-mssqldb v0.0.0-20190204142019-df6d76eb9289
github.com/dgrijalva/jwt-go => github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go => github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dgryski/go-bitstream => github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8
github.com/dgryski/go-sip13 => github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b
github.com/disintegration/imaging => github.com/disintegration/imaging v1.6.1 github.com/disintegration/imaging => github.com/disintegration/imaging v1.6.1
github.com/docker/cli => github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d github.com/docker/cli => github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d
github.com/docker/distribution => github.com/docker/distribution v2.7.1+incompatible github.com/docker/distribution => github.com/docker/distribution v2.7.1+incompatible
@@ -185,6 +210,8 @@ replace (
github.com/docker/spdystream => github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c github.com/docker/spdystream => github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c
github.com/docopt/docopt-go => github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/docopt/docopt-go => github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/dustin/go-humanize => github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize => github.com/dustin/go-humanize v1.0.0
github.com/eclipse/paho.mqtt.golang => github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/edsrzf/mmap-go => github.com/edsrzf/mmap-go v1.0.0
github.com/elastic/go-elasticsearch/v5 => github.com/elastic/go-elasticsearch/v5 v5.6.1 github.com/elastic/go-elasticsearch/v5 => github.com/elastic/go-elasticsearch/v5 v5.6.1
github.com/elastic/go-elasticsearch/v6 => github.com/elastic/go-elasticsearch/v6 v6.8.2 github.com/elastic/go-elasticsearch/v6 => github.com/elastic/go-elasticsearch/v6 v6.8.2
github.com/elastic/go-elasticsearch/v7 => github.com/elastic/go-elasticsearch/v7 v7.3.0 github.com/elastic/go-elasticsearch/v7 => github.com/elastic/go-elasticsearch/v7 v7.3.0
@@ -207,6 +234,8 @@ replace (
github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.4.0 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.4.0
github.com/gliderlabs/ssh => github.com/gliderlabs/ssh v0.1.1 github.com/gliderlabs/ssh => github.com/gliderlabs/ssh v0.1.1
github.com/globalsign/mgo => github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 github.com/globalsign/mgo => github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
github.com/glycerine/go-unsnap-stream => github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd
github.com/glycerine/goconvey => github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31
github.com/go-kit/kit => github.com/go-kit/kit v0.8.0 github.com/go-kit/kit => github.com/go-kit/kit v0.8.0
github.com/go-ldap/ldap => github.com/go-ldap/ldap v3.0.3+incompatible github.com/go-ldap/ldap => github.com/go-ldap/ldap v3.0.3+incompatible
github.com/go-logfmt/logfmt => github.com/go-logfmt/logfmt v0.4.0 github.com/go-logfmt/logfmt => github.com/go-logfmt/logfmt v0.4.0
@@ -228,13 +257,25 @@ replace (
github.com/go-redis/redis => github.com/go-redis/redis v6.15.2+incompatible github.com/go-redis/redis => github.com/go-redis/redis v6.15.2+incompatible
github.com/go-sql-driver/mysql => github.com/go-sql-driver/mysql v1.4.1 github.com/go-sql-driver/mysql => github.com/go-sql-driver/mysql v1.4.1
github.com/go-stack/stack => github.com/go-stack/stack v1.8.0 github.com/go-stack/stack => github.com/go-stack/stack v1.8.0
github.com/gobuffalo/attrs => github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd
github.com/gobuffalo/depgen => github.com/gobuffalo/depgen v0.1.0
github.com/gobuffalo/envy => github.com/gobuffalo/envy v1.7.0
github.com/gobuffalo/flect => github.com/gobuffalo/flect v0.1.5 github.com/gobuffalo/flect => github.com/gobuffalo/flect v0.1.5
github.com/gobuffalo/genny => github.com/gobuffalo/genny v0.1.1
github.com/gobuffalo/gitgen => github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211
github.com/gobuffalo/gogen => github.com/gobuffalo/gogen v0.1.1
github.com/gobuffalo/logger => github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2
github.com/gobuffalo/mapi => github.com/gobuffalo/mapi v1.0.2
github.com/gobuffalo/packd => github.com/gobuffalo/packd v0.1.0
github.com/gobuffalo/packr/v2 => github.com/gobuffalo/packr/v2 v2.2.0
github.com/gobuffalo/syncx => github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754
github.com/gobwas/glob => github.com/gobwas/glob v0.2.3 github.com/gobwas/glob => github.com/gobwas/glob v0.2.3
github.com/gocraft/dbr => github.com/gocraft/dbr v0.0.0-20180507214907-a0fd650918f6 github.com/gocraft/dbr => github.com/gocraft/dbr v0.0.0-20180507214907-a0fd650918f6
github.com/gofrs/flock => github.com/gofrs/flock v0.7.1 github.com/gofrs/flock => github.com/gofrs/flock v0.7.1
github.com/gofrs/uuid => github.com/gofrs/uuid v3.2.0+incompatible github.com/gofrs/uuid => github.com/gofrs/uuid v3.2.0+incompatible
github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.0 github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.0
github.com/golang/example => github.com/golang/example v0.0.0-20170904185048-46695d81d1fa github.com/golang/example => github.com/golang/example v0.0.0-20170904185048-46695d81d1fa
github.com/golang/geo => github.com/golang/geo v0.0.0-20190916061304-5b978397cfec
github.com/golang/glog => github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/glog => github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/groupcache => github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 github.com/golang/groupcache => github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6
github.com/golang/mock => github.com/golang/mock v1.2.0 github.com/golang/mock => github.com/golang/mock v1.2.0
@@ -244,6 +285,7 @@ replace (
github.com/golangplus/fmt => github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995 github.com/golangplus/fmt => github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995
github.com/golangplus/testing => github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e github.com/golangplus/testing => github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e
github.com/google/btree => github.com/google/btree v1.0.0 github.com/google/btree => github.com/google/btree v1.0.0
github.com/google/flatbuffers => github.com/google/flatbuffers v1.11.0
github.com/google/go-cmp => github.com/google/go-cmp v0.3.0 github.com/google/go-cmp => github.com/google/go-cmp v0.3.0
github.com/google/go-querystring => github.com/google/go-querystring v1.0.0 github.com/google/go-querystring => github.com/google/go-querystring v1.0.0
github.com/google/gofuzz => github.com/google/gofuzz v1.0.0 github.com/google/gofuzz => github.com/google/gofuzz v1.0.0
@@ -254,6 +296,7 @@ replace (
github.com/googleapis/gax-go/v2 => github.com/googleapis/gax-go/v2 v2.0.4 github.com/googleapis/gax-go/v2 => github.com/googleapis/gax-go/v2 v2.0.4
github.com/googleapis/gnostic => github.com/googleapis/gnostic v0.3.1 github.com/googleapis/gnostic => github.com/googleapis/gnostic v0.3.1
github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.3.0 github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.3.0
github.com/gopherjs/gopherjs => github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1
github.com/gorilla/handlers => github.com/gorilla/handlers v1.4.0 github.com/gorilla/handlers => github.com/gorilla/handlers v1.4.0
github.com/gorilla/mux => github.com/gorilla/mux v1.7.1 github.com/gorilla/mux => github.com/gorilla/mux v1.7.1
github.com/gorilla/websocket => github.com/gorilla/websocket v1.4.0 github.com/gorilla/websocket => github.com/gorilla/websocket v1.4.0
@@ -262,30 +305,65 @@ replace (
github.com/grpc-ecosystem/go-grpc-middleware => github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/grpc-ecosystem/go-grpc-middleware => github.com/grpc-ecosystem/go-grpc-middleware v1.0.0
github.com/grpc-ecosystem/go-grpc-prometheus => github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/go-grpc-prometheus => github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/grpc-ecosystem/grpc-gateway => github.com/grpc-ecosystem/grpc-gateway v1.11.3 github.com/grpc-ecosystem/grpc-gateway => github.com/grpc-ecosystem/grpc-gateway v1.11.3
github.com/hashicorp/consul/api => github.com/hashicorp/consul/api v1.4.0
github.com/hashicorp/consul/sdk => github.com/hashicorp/consul/sdk v0.4.0
github.com/hashicorp/errwrap => github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/go-cleanhttp => github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-hclog => github.com/hashicorp/go-hclog v0.12.2
github.com/hashicorp/go-immutable-radix => github.com/hashicorp/go-immutable-radix v1.2.0
github.com/hashicorp/go-msgpack => github.com/hashicorp/go-msgpack v0.5.3
github.com/hashicorp/go-multierror => github.com/hashicorp/go-multierror v1.0.0
github.com/hashicorp/go-retryablehttp => github.com/hashicorp/go-retryablehttp v0.5.3
github.com/hashicorp/go-rootcerts => github.com/hashicorp/go-rootcerts v1.0.2
github.com/hashicorp/go-sockaddr => github.com/hashicorp/go-sockaddr v1.0.2
github.com/hashicorp/go-syslog => github.com/hashicorp/go-syslog v1.0.0
github.com/hashicorp/go-uuid => github.com/hashicorp/go-uuid v1.0.1
github.com/hashicorp/go-version => github.com/hashicorp/go-version v1.2.0 github.com/hashicorp/go-version => github.com/hashicorp/go-version v1.2.0
github.com/hashicorp/golang-lru => github.com/hashicorp/golang-lru v0.5.3 github.com/hashicorp/golang-lru => github.com/hashicorp/golang-lru v0.5.3
github.com/hashicorp/hcl => github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hcl => github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/logutils => github.com/hashicorp/logutils v1.0.0
github.com/hashicorp/mdns => github.com/hashicorp/mdns v1.0.1
github.com/hashicorp/memberlist => github.com/hashicorp/memberlist v0.2.0
github.com/hashicorp/serf => github.com/hashicorp/serf v0.9.0
github.com/hpcloud/tail => github.com/hpcloud/tail v1.0.0 github.com/hpcloud/tail => github.com/hpcloud/tail v1.0.0
github.com/huandu/xstrings => github.com/huandu/xstrings v1.2.0 github.com/huandu/xstrings => github.com/huandu/xstrings v1.2.0
github.com/imdario/mergo => github.com/imdario/mergo v0.3.7 github.com/imdario/mergo => github.com/imdario/mergo v0.3.7
github.com/inconshreveable/mousetrap => github.com/inconshreveable/mousetrap v1.0.0 github.com/inconshreveable/mousetrap => github.com/inconshreveable/mousetrap v1.0.0
github.com/influxdata/flux => github.com/influxdata/flux v0.65.0
github.com/influxdata/influxdb => github.com/influxdata/influxdb v1.8.0
github.com/influxdata/influxql => github.com/influxdata/influxql v1.1.0
github.com/influxdata/line-protocol => github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e
github.com/influxdata/promql/v2 => github.com/influxdata/promql/v2 v2.12.0
github.com/influxdata/roaring => github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6
github.com/influxdata/tdigest => github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9
github.com/influxdata/usage-client => github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368
github.com/jbenet/go-context => github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 github.com/jbenet/go-context => github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
github.com/jessevdk/go-flags => github.com/jessevdk/go-flags v1.4.0 github.com/jessevdk/go-flags => github.com/jessevdk/go-flags v1.4.0
github.com/jinzhu/gorm => github.com/jinzhu/gorm v1.9.2 github.com/jinzhu/gorm => github.com/jinzhu/gorm v1.9.2
github.com/jinzhu/inflection => github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a github.com/jinzhu/inflection => github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a
github.com/jinzhu/now => github.com/jinzhu/now v1.0.0 github.com/jinzhu/now => github.com/jinzhu/now v1.0.0
github.com/jmespath/go-jmespath => github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af github.com/jmespath/go-jmespath => github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
github.com/joho/godotenv => github.com/joho/godotenv v1.3.0
github.com/jonboulle/clockwork => github.com/jonboulle/clockwork v0.1.0 github.com/jonboulle/clockwork => github.com/jonboulle/clockwork v0.1.0
github.com/jpillora/backoff => github.com/jpillora/backoff v1.0.0
github.com/json-iterator/go => github.com/json-iterator/go v1.1.8 github.com/json-iterator/go => github.com/json-iterator/go v1.1.8
github.com/jstemmer/go-junit-report => github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 github.com/jstemmer/go-junit-report => github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024
github.com/jsternberg/zap-logfmt => github.com/jsternberg/zap-logfmt v1.0.0
github.com/jtolds/gls => github.com/jtolds/gls v4.20.0+incompatible
github.com/julienschmidt/httprouter => github.com/julienschmidt/httprouter v1.2.0 github.com/julienschmidt/httprouter => github.com/julienschmidt/httprouter v1.2.0
github.com/jwilder/encoding => github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef
github.com/kardianos/osext => github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 github.com/kardianos/osext => github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
github.com/karrick/godirwalk => github.com/karrick/godirwalk v1.10.3
github.com/kelseyhightower/envconfig => github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig => github.com/kelseyhightower/envconfig v1.4.0
github.com/kevinburke/ssh_config => github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e github.com/kevinburke/ssh_config => github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e
github.com/keybase/go-ps => github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999 github.com/keybase/go-ps => github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999
github.com/kiali/kiali => github.com/kubesphere/kiali v0.15.1-0.20201110082537-0c2b977257d4 github.com/kiali/kiali => github.com/kubesphere/kiali v0.15.1-0.20201110082537-0c2b977257d4
github.com/kisielk/errcheck => github.com/kisielk/errcheck v1.2.0 github.com/kisielk/errcheck => github.com/kisielk/errcheck v1.2.0
github.com/kisielk/gotool => github.com/kisielk/gotool v1.0.0 github.com/kisielk/gotool => github.com/kisielk/gotool v1.0.0
github.com/klauspost/compress => github.com/klauspost/compress v1.9.5
github.com/klauspost/cpuid => github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5
github.com/klauspost/crc32 => github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6
github.com/klauspost/pgzip => github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada
github.com/koding/multiconfig => github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7 github.com/koding/multiconfig => github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7
github.com/konsorten/go-windows-terminal-sequences => github.com/konsorten/go-windows-terminal-sequences v1.0.2 github.com/konsorten/go-windows-terminal-sequences => github.com/konsorten/go-windows-terminal-sequences v1.0.2
github.com/kr/logfmt => github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 github.com/kr/logfmt => github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515
@@ -296,31 +374,41 @@ replace (
github.com/kubernetes-csi/csi-test => github.com/kubernetes-csi/csi-test v2.0.0+incompatible github.com/kubernetes-csi/csi-test => github.com/kubernetes-csi/csi-test v2.0.0+incompatible
github.com/kubernetes-csi/external-snapshotter/v2 => github.com/kubernetes-csi/external-snapshotter/v2 v2.1.0 github.com/kubernetes-csi/external-snapshotter/v2 => github.com/kubernetes-csi/external-snapshotter/v2 v2.1.0
github.com/kubesphere/sonargo => github.com/kubesphere/sonargo v0.0.2 github.com/kubesphere/sonargo => github.com/kubesphere/sonargo v0.0.2
github.com/kylelemons/godebug => github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb
github.com/leodido/go-urn => github.com/leodido/go-urn v1.1.0 github.com/leodido/go-urn => github.com/leodido/go-urn v1.1.0
github.com/lib/pq => github.com/lib/pq v1.2.0 github.com/lib/pq => github.com/lib/pq v1.2.0
github.com/liggitt/tabwriter => github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de github.com/liggitt/tabwriter => github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
github.com/lithammer/dedent => github.com/lithammer/dedent v1.1.0 github.com/lithammer/dedent => github.com/lithammer/dedent v1.1.0
github.com/magiconair/properties => github.com/magiconair/properties v1.8.0 github.com/magiconair/properties => github.com/magiconair/properties v1.8.0
github.com/mailru/easyjson => github.com/mailru/easyjson v0.7.0 github.com/mailru/easyjson => github.com/mailru/easyjson v0.7.0
github.com/markbates/oncer => github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2
github.com/markbates/safe => github.com/markbates/safe v1.0.1
github.com/mattn/go-colorable => github.com/mattn/go-colorable v0.1.2 github.com/mattn/go-colorable => github.com/mattn/go-colorable v0.1.2
github.com/mattn/go-isatty => github.com/mattn/go-isatty v0.0.8 github.com/mattn/go-isatty => github.com/mattn/go-isatty v0.0.8
github.com/mattn/go-runewidth => github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39 github.com/mattn/go-runewidth => github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39
github.com/mattn/go-shellwords => github.com/mattn/go-shellwords v1.0.5 github.com/mattn/go-shellwords => github.com/mattn/go-shellwords v1.0.5
github.com/mattn/go-sqlite3 => github.com/mattn/go-sqlite3 v1.11.0 github.com/mattn/go-sqlite3 => github.com/mattn/go-sqlite3 v1.11.0
github.com/mattn/go-tty => github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104
github.com/matttproud/golang_protobuf_extensions => github.com/matttproud/golang_protobuf_extensions v1.0.1 github.com/matttproud/golang_protobuf_extensions => github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/miekg/dns => github.com/miekg/dns v0.0.0-20181005163659-0d29b283ac0f github.com/miekg/dns => github.com/miekg/dns v0.0.0-20181005163659-0d29b283ac0f
github.com/mitchellh/cli => github.com/mitchellh/cli v1.0.0
github.com/mitchellh/copystructure => github.com/mitchellh/copystructure v1.0.0 github.com/mitchellh/copystructure => github.com/mitchellh/copystructure v1.0.0
github.com/mitchellh/go-homedir => github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir => github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-testing-interface => github.com/mitchellh/go-testing-interface v1.0.0
github.com/mitchellh/go-wordwrap => github.com/mitchellh/go-wordwrap v1.0.0 github.com/mitchellh/go-wordwrap => github.com/mitchellh/go-wordwrap v1.0.0
github.com/mitchellh/mapstructure => github.com/mitchellh/mapstructure v1.1.2 github.com/mitchellh/mapstructure => github.com/mitchellh/mapstructure v1.1.2
github.com/mitchellh/reflectwalk => github.com/mitchellh/reflectwalk v1.0.0 github.com/mitchellh/reflectwalk => github.com/mitchellh/reflectwalk v1.0.0
github.com/mna/pigeon => github.com/mna/pigeon v0.0.0-20180808201053-bb0192cfc2ae github.com/mna/pigeon => github.com/mna/pigeon v0.0.0-20180808201053-bb0192cfc2ae
github.com/modern-go/concurrent => github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd github.com/modern-go/concurrent => github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
github.com/modern-go/reflect2 => github.com/modern-go/reflect2 v1.0.1 github.com/modern-go/reflect2 => github.com/modern-go/reflect2 v1.0.1
github.com/montanaflynn/stats => github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe
github.com/morikuni/aec => github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c github.com/morikuni/aec => github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c
github.com/mschoch/smat => github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae
github.com/munnerz/goautoneg => github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d github.com/munnerz/goautoneg => github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d
github.com/mwitkow/go-conntrack => github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 github.com/mwitkow/go-conntrack => github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223
github.com/mxk/go-flowrate => github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f github.com/mxk/go-flowrate => github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
github.com/oklog/run => github.com/oklog/run v1.1.0
github.com/oklog/ulid => github.com/oklog/ulid v1.3.1
github.com/olekukonko/tablewriter => github.com/olekukonko/tablewriter v0.0.1 github.com/olekukonko/tablewriter => github.com/olekukonko/tablewriter v0.0.1
github.com/onsi/ginkgo => github.com/onsi/ginkgo v1.8.0 github.com/onsi/ginkgo => github.com/onsi/ginkgo v1.8.0
github.com/onsi/gomega => github.com/onsi/gomega v1.5.0 github.com/onsi/gomega => github.com/onsi/gomega v1.5.0
@@ -330,8 +418,11 @@ replace (
github.com/opencontainers/runc => github.com/opencontainers/runc v0.1.1 github.com/opencontainers/runc => github.com/opencontainers/runc v0.1.1
github.com/openshift/api => github.com/openshift/api v0.0.0-20180801171038-322a19404e37 github.com/openshift/api => github.com/openshift/api v0.0.0-20180801171038-322a19404e37
github.com/openshift/generic-admission-server => github.com/openshift/generic-admission-server v1.14.0 github.com/openshift/generic-admission-server => github.com/openshift/generic-admission-server v1.14.0
github.com/opentracing-contrib/go-stdlib => github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9
github.com/opentracing/opentracing-go => github.com/opentracing/opentracing-go v1.1.0 github.com/opentracing/opentracing-go => github.com/opentracing/opentracing-go v1.1.0
github.com/pascaldekloe/goe => github.com/pascaldekloe/goe v0.1.0
github.com/patrickmn/go-cache => github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache => github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/paulbellamy/ratecounter => github.com/paulbellamy/ratecounter v0.2.0
github.com/pborman/uuid => github.com/pborman/uuid v1.2.0 github.com/pborman/uuid => github.com/pborman/uuid v1.2.0
github.com/pelletier/go-buffruneio => github.com/pelletier/go-buffruneio v0.2.0 github.com/pelletier/go-buffruneio => github.com/pelletier/go-buffruneio v0.2.0
github.com/pelletier/go-toml => github.com/pelletier/go-toml v1.2.0 github.com/pelletier/go-toml => github.com/pelletier/go-toml v1.2.0
@@ -339,8 +430,11 @@ replace (
github.com/peterh/liner => github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d github.com/peterh/liner => github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d
github.com/phayes/freeport => github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5 github.com/phayes/freeport => github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5
github.com/philhofer/fwd => github.com/philhofer/fwd v1.0.0 github.com/philhofer/fwd => github.com/philhofer/fwd v1.0.0
github.com/pierrec/lz4 => github.com/pierrec/lz4 v2.0.5+incompatible
github.com/pkg/errors => github.com/pkg/errors v0.8.1 github.com/pkg/errors => github.com/pkg/errors v0.8.1
github.com/pkg/term => github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5
github.com/pmezard/go-difflib => github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib => github.com/pmezard/go-difflib v1.0.0
github.com/posener/complete => github.com/posener/complete v1.1.1
github.com/pquerna/cachecontrol => github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 github.com/pquerna/cachecontrol => github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021
github.com/pquerna/ffjson => github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9 github.com/pquerna/ffjson => github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9
github.com/projectcalico/go-json => github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba github.com/projectcalico/go-json => github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba
@@ -348,6 +442,8 @@ replace (
github.com/projectcalico/go-yaml-wrapper => github.com/projectcalico/go-yaml-wrapper v0.0.0-20161127220527-598e54215bee github.com/projectcalico/go-yaml-wrapper => github.com/projectcalico/go-yaml-wrapper v0.0.0-20161127220527-598e54215bee
github.com/projectcalico/kube-controllers => github.com/projectcalico/kube-controllers v3.8.8+incompatible github.com/projectcalico/kube-controllers => github.com/projectcalico/kube-controllers v3.8.8+incompatible
github.com/projectcalico/libcalico-go => github.com/projectcalico/libcalico-go v1.7.2-0.20191104213956-8f81e1e344ce github.com/projectcalico/libcalico-go => github.com/projectcalico/libcalico-go v1.7.2-0.20191104213956-8f81e1e344ce
github.com/prometheus-community/prom-label-proxy => github.com/prometheus-community/prom-label-proxy v0.2.0
github.com/prometheus/alertmanager => github.com/prometheus/alertmanager v0.20.0
github.com/prometheus/client_golang => github.com/prometheus/client_golang v1.5.1 github.com/prometheus/client_golang => github.com/prometheus/client_golang v1.5.1
github.com/prometheus/client_model => github.com/prometheus/client_model v0.2.0 github.com/prometheus/client_model => github.com/prometheus/client_model v0.2.0
github.com/prometheus/common => github.com/prometheus/common v0.9.1 github.com/prometheus/common => github.com/prometheus/common v0.9.1
@@ -355,15 +451,26 @@ replace (
github.com/prometheus/prometheus => github.com/prometheus/prometheus v1.8.2-0.20200507164740-ecee9c8abfd1 github.com/prometheus/prometheus => github.com/prometheus/prometheus v1.8.2-0.20200507164740-ecee9c8abfd1
github.com/rcrowley/go-metrics => github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a github.com/rcrowley/go-metrics => github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a
github.com/remyoudompheng/bigfft => github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446 github.com/remyoudompheng/bigfft => github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446
github.com/retailnext/hllpp => github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52
github.com/robfig/cron => github.com/robfig/cron v1.2.0 github.com/robfig/cron => github.com/robfig/cron v1.2.0
github.com/rogpeppe/fastuuid => github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af github.com/rogpeppe/fastuuid => github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af
github.com/rogpeppe/go-charset => github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4 github.com/rogpeppe/go-charset => github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4
github.com/rogpeppe/go-internal => github.com/rogpeppe/go-internal v1.3.0
github.com/rs/cors => github.com/rs/cors v1.6.0
github.com/russross/blackfriday => github.com/russross/blackfriday v1.5.2 github.com/russross/blackfriday => github.com/russross/blackfriday v1.5.2
github.com/ryanuber/columnize => github.com/ryanuber/columnize v2.1.0+incompatible
github.com/samuel/go-zookeeper => github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da
github.com/satori/go.uuid => github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid => github.com/satori/go.uuid v1.2.0
github.com/sean-/seed => github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529
github.com/segmentio/kafka-go => github.com/segmentio/kafka-go v0.2.0
github.com/sergi/go-diff => github.com/sergi/go-diff v1.0.0 github.com/sergi/go-diff => github.com/sergi/go-diff v1.0.0
github.com/shirou/gopsutil => github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7 github.com/shirou/gopsutil => github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7
github.com/shirou/w32 => github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 github.com/shirou/w32 => github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4
github.com/shurcooL/httpfs => github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/shurcooL/vfsgen => github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
github.com/sirupsen/logrus => github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus => github.com/sirupsen/logrus v1.4.2
github.com/smartystreets/assertions => github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d
github.com/smartystreets/goconvey => github.com/smartystreets/goconvey v1.6.4
github.com/soheilhy/cmux => github.com/soheilhy/cmux v0.1.4 github.com/soheilhy/cmux => github.com/soheilhy/cmux v0.1.4
github.com/sony/sonyflake => github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009 github.com/sony/sonyflake => github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009
github.com/spaolacci/murmur3 => github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 github.com/spaolacci/murmur3 => github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72
@@ -377,13 +484,19 @@ replace (
github.com/src-d/gcfg => github.com/src-d/gcfg v1.4.0 github.com/src-d/gcfg => github.com/src-d/gcfg v1.4.0
github.com/stretchr/objx => github.com/stretchr/objx v0.2.0 github.com/stretchr/objx => github.com/stretchr/objx v0.2.0
github.com/stretchr/testify => github.com/stretchr/testify v1.4.0 github.com/stretchr/testify => github.com/stretchr/testify v1.4.0
github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.0 github.com/tidwall/pretty => github.com/tidwall/pretty v1.0.0
github.com/tinylib/msgp => github.com/tinylib/msgp v1.1.0 github.com/tinylib/msgp => github.com/tinylib/msgp v1.1.0
github.com/tmc/grpc-websocket-proxy => github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 github.com/tmc/grpc-websocket-proxy => github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5
github.com/tv42/httpunix => github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926
github.com/uber/jaeger-client-go => github.com/uber/jaeger-client-go v2.23.0+incompatible
github.com/uber/jaeger-lib => github.com/uber/jaeger-lib v2.2.0+incompatible
github.com/ugorji/go => github.com/ugorji/go v1.1.4 github.com/ugorji/go => github.com/ugorji/go v1.1.4
github.com/ugorji/go/codec => github.com/ugorji/go/codec v0.0.0-20190128213124-ee1426cffec0 github.com/ugorji/go/codec => github.com/ugorji/go/codec v0.0.0-20190128213124-ee1426cffec0
github.com/urfave/cli => github.com/urfave/cli v1.20.0 github.com/urfave/cli => github.com/urfave/cli v1.20.0
github.com/willf/bitset => github.com/willf/bitset v1.1.3
github.com/xanzy/ssh-agent => github.com/xanzy/ssh-agent v0.2.1 github.com/xanzy/ssh-agent => github.com/xanzy/ssh-agent v0.2.1
github.com/xdg/scram => github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c
github.com/xdg/stringprep => github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc
github.com/xeipuuv/gojsonpointer => github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f github.com/xeipuuv/gojsonpointer => github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f
github.com/xeipuuv/gojsonreference => github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 github.com/xeipuuv/gojsonreference => github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415
github.com/xeipuuv/gojsonschema => github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema => github.com/xeipuuv/gojsonschema v1.2.0
@@ -398,6 +511,7 @@ replace (
github.com/yvasiyarov/newrelic_platform_go => github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f github.com/yvasiyarov/newrelic_platform_go => github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f
go.etcd.io/bbolt => go.etcd.io/bbolt v1.3.3 go.etcd.io/bbolt => go.etcd.io/bbolt v1.3.3
go.etcd.io/etcd => go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 go.etcd.io/etcd => go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738
go.mongodb.org/mongo-driver => go.mongodb.org/mongo-driver v1.3.2
go.opencensus.io => go.opencensus.io v0.21.0 go.opencensus.io => go.opencensus.io v0.21.0
go.uber.org/atomic => go.uber.org/atomic v1.4.0 go.uber.org/atomic => go.uber.org/atomic v1.4.0
go.uber.org/multierr => go.uber.org/multierr v1.1.0 go.uber.org/multierr => go.uber.org/multierr v1.1.0
@@ -427,7 +541,9 @@ replace (
gopkg.in/asn1-ber.v1 => gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d gopkg.in/asn1-ber.v1 => gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d
gopkg.in/check.v1 => gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 gopkg.in/check.v1 => gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
gopkg.in/cheggaaa/pb.v1 => gopkg.in/cheggaaa/pb.v1 v1.0.25 gopkg.in/cheggaaa/pb.v1 => gopkg.in/cheggaaa/pb.v1 v1.0.25
gopkg.in/errgo.v2 => gopkg.in/errgo.v2 v2.1.0
gopkg.in/fsnotify.v1 => gopkg.in/fsnotify.v1 v1.4.7 gopkg.in/fsnotify.v1 => gopkg.in/fsnotify.v1 v1.4.7
gopkg.in/fsnotify/fsnotify.v1 => gopkg.in/fsnotify/fsnotify.v1 v1.4.7
gopkg.in/gemnasium/logrus-airbrake-hook.v2 => gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 gopkg.in/gemnasium/logrus-airbrake-hook.v2 => gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2
gopkg.in/go-playground/assert.v1 => gopkg.in/go-playground/assert.v1 v1.2.1 gopkg.in/go-playground/assert.v1 => gopkg.in/go-playground/assert.v1 v1.2.1
gopkg.in/go-playground/validator.v8 => gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/go-playground/validator.v8 => gopkg.in/go-playground/validator.v8 v8.18.2
@@ -480,6 +596,7 @@ replace (
openpitrix.io/logger => openpitrix.io/logger v0.1.0 openpitrix.io/logger => openpitrix.io/logger v0.1.0
openpitrix.io/notification => openpitrix.io/notification v0.2.2 openpitrix.io/notification => openpitrix.io/notification v0.2.2
openpitrix.io/openpitrix => openpitrix.io/openpitrix v0.4.9-0.20200611125425-ae07f141e797 openpitrix.io/openpitrix => openpitrix.io/openpitrix v0.4.9-0.20200611125425-ae07f141e797
rsc.io/binaryregexp => rsc.io/binaryregexp v0.2.0
rsc.io/goversion => rsc.io/goversion v1.0.0 rsc.io/goversion => rsc.io/goversion v1.0.0
rsc.io/letsencrypt => rsc.io/letsencrypt v0.0.1 rsc.io/letsencrypt => rsc.io/letsencrypt v0.0.1
sigs.k8s.io/application => kubesphere.io/application v1.0.0 sigs.k8s.io/application => kubesphere.io/application v1.0.0

32
go.sum
View File

@@ -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/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/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/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= 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 h1:tW+ztA4A9UT9xnco5wUjW1oNi35k22eUEn9tNpPYVwE=
code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc= 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/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/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/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-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/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= 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 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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/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/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/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 h1:xpKq9ap8MbYfhuPCF0dBH854Gp9CxZjr/IocxELFflo=
github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= 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/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/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/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/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/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/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/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/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 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/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/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/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-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-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-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-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-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-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 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-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-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-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 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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/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/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/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/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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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/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/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/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/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 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= 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/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/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/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/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 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= 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-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-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/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 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 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/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 h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 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/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 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-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 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 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/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 h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 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/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/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-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/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/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/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/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/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/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/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/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 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=

View File

@@ -1,51 +0,0 @@
/*
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 auth
import "fmt"
const (
KindTokenReview = "TokenReview"
)
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
}

View File

@@ -18,6 +18,7 @@ package api
import ( import (
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog" "k8s.io/klog"
"net/http" "net/http"
"runtime" "runtime"
@@ -28,32 +29,49 @@ import (
var sanitizer = strings.NewReplacer(`&`, "&amp;", `<`, "&lt;", `>`, "&gt;") var sanitizer = strings.NewReplacer(`&`, "&amp;", `<`, "&lt;", `>`, "&gt;")
func HandleInternalError(response *restful.Response, req *restful.Request, err error) { func HandleInternalError(response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(1) handle(http.StatusInternalServerError, response, req, err)
klog.Errorf("%s:%d %v", fn, line, err)
http.Error(response, sanitizer.Replace(err.Error()), http.StatusInternalServerError)
} }
// HandleBadRequest writes http.StatusBadRequest and log error // HandleBadRequest writes http.StatusBadRequest and log error
func HandleBadRequest(response *restful.Response, req *restful.Request, err error) { func HandleBadRequest(response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(1) handle(http.StatusBadRequest, response, req, err)
klog.Errorf("%s:%d %v", fn, line, err)
http.Error(response, sanitizer.Replace(err.Error()), http.StatusBadRequest)
} }
func HandleNotFound(response *restful.Response, req *restful.Request, err error) { func HandleNotFound(response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(1) handle(http.StatusNotFound, response, req, err)
klog.Errorf("%s:%d %v", fn, line, err)
http.Error(response, sanitizer.Replace(err.Error()), http.StatusNotFound)
} }
func HandleForbidden(response *restful.Response, req *restful.Request, err error) { func HandleForbidden(response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(1) handle(http.StatusForbidden, response, req, err)
klog.Errorf("%s:%d %v", fn, line, err) }
http.Error(response, sanitizer.Replace(err.Error()), http.StatusForbidden)
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) { func HandleConflict(response *restful.Response, req *restful.Request, err error) {
_, fn, line, _ := runtime.Caller(1) handle(http.StatusConflict, response, req, err)
klog.Errorf("%s:%d %v", fn, line, err) }
http.Error(response, sanitizer.Replace(err.Error()), http.StatusConflict)
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()), statusCode)
} }

View File

@@ -58,13 +58,20 @@ const (
GlobalRoleAnnotation = "iam.kubesphere.io/globalrole" GlobalRoleAnnotation = "iam.kubesphere.io/globalrole"
WorkspaceRoleAnnotation = "iam.kubesphere.io/workspacerole" WorkspaceRoleAnnotation = "iam.kubesphere.io/workspacerole"
ClusterRoleAnnotation = "iam.kubesphere.io/clusterrole" ClusterRoleAnnotation = "iam.kubesphere.io/clusterrole"
UninitializedAnnotation = "iam.kubesphere.io/uninitialized"
RoleAnnotation = "iam.kubesphere.io/role" RoleAnnotation = "iam.kubesphere.io/role"
RoleTemplateLabel = "iam.kubesphere.io/role-template" RoleTemplateLabel = "iam.kubesphere.io/role-template"
ScopeLabelFormat = "scope.kubesphere.io/%s" ScopeLabelFormat = "scope.kubesphere.io/%s"
UserReferenceLabel = "iam.kubesphere.io/user-ref" UserReferenceLabel = "iam.kubesphere.io/user-ref"
IdentifyProviderLabel = "iam.kubesphere.io/identify-provider" IdentifyProviderLabel = "iam.kubesphere.io/identify-provider"
PasswordEncryptedAnnotation = "iam.kubesphere.io/password-encrypted" OriginUIDLabel = "iam.kubesphere.io/origin-uid"
FieldEmail = "email" FieldEmail = "email"
ExtraEmail = FieldEmail
ExtraIdentityProvider = "idp"
ExtraUID = "uid"
ExtraUsername = "username"
ExtraDisplayName = "displayName"
ExtraUninitialized = "uninitialized"
InGroup = "ingroup" InGroup = "ingroup"
NotInGroup = "notingroup" NotInGroup = "notingroup"
AggregateTo = "aggregateTo" AggregateTo = "aggregateTo"
@@ -76,6 +83,8 @@ const (
NamespaceAdmin = "admin" NamespaceAdmin = "admin"
WorkspaceAdminFormat = "%s-admin" WorkspaceAdminFormat = "%s-admin"
ClusterAdmin = "cluster-admin" ClusterAdmin = "cluster-admin"
PreRegistrationUser = "system:pre-registration"
PreRegistrationUserGroup = "pre-registration"
) )
// +genclient // +genclient
@@ -87,6 +96,7 @@ const (
// +kubebuilder:printcolumn:name="Email",type="string",JSONPath=".spec.email" // +kubebuilder:printcolumn:name="Email",type="string",JSONPath=".spec.email"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state" // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state"
// +kubebuilder:resource:categories="iam",scope="Cluster" // +kubebuilder:resource:categories="iam",scope="Cluster"
// +kubebuilder:subresource:status
type User struct { type User struct {
metav1.TypeMeta `json:",inline"` metav1.TypeMeta `json:",inline"`
// Standard object's metadata. // Standard object's metadata.
@@ -126,7 +136,7 @@ const (
UserActive UserState = "Active" UserActive UserState = "Active"
// UserDisabled means the user is disabled. // UserDisabled means the user is disabled.
UserDisabled UserState = "Disabled" UserDisabled UserState = "Disabled"
// UserDisabled means the user is disabled. // UserAuthLimitExceeded means restrict user login.
UserAuthLimitExceeded UserState = "AuthLimitExceeded" UserAuthLimitExceeded UserState = "AuthLimitExceeded"
AuthenticatedSuccessfully = "authenticated successfully" AuthenticatedSuccessfully = "authenticated successfully"
@@ -136,7 +146,7 @@ const (
type UserStatus struct { type UserStatus struct {
// The user status // The user status
// +optional // +optional
State UserState `json:"state,omitempty"` State *UserState `json:"state,omitempty"`
// +optional // +optional
Reason string `json:"reason,omitempty"` Reason string `json:"reason,omitempty"`
// +optional // +optional

View File

@@ -20,6 +20,10 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "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" "net/http"
rt "runtime" rt "runtime"
"time" "time"
@@ -167,9 +171,15 @@ func (s *APIServer) PrepareRun(stopCh <-chan struct{}) error {
// Installation happens before all informers start to cache objects, so // Installation happens before all informers start to cache objects, so
// any attempt to list objects using listers will get empty results. // any attempt to list objects using listers will get empty results.
func (s *APIServer) installKubeSphereAPIs() { func (s *APIServer) installKubeSphereAPIs() {
imOperator := im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory, s.Config.AuthenticationOptions) imOperator := im.NewOperator(s.KubernetesClient.KubeSphere(),
amOperator := am.NewOperator(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes()) user.New(s.InformerFactory.KubeSphereSharedInformerFactory(),
rbacAuthorizer := authorizerfactory.NewRBACAuthorizer(amOperator) 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(configv1alpha2.AddToContainer(s.container, s.Config))
urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory)) urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory))
@@ -187,20 +197,22 @@ func (s *APIServer) installKubeSphereAPIs() {
s.Config.MultiClusterOptions.ProxyPublishService, s.Config.MultiClusterOptions.ProxyPublishService,
s.Config.MultiClusterOptions.ProxyPublishAddress, s.Config.MultiClusterOptions.ProxyPublishAddress,
s.Config.MultiClusterOptions.AgentImage)) s.Config.MultiClusterOptions.AgentImage))
urlruntime.Must(iamapi.AddToContainer(s.container, imOperator, urlruntime.Must(iamapi.AddToContainer(s.container, imOperator, amOperator,
am.NewOperator(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes()),
group.New(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes()), group.New(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes()),
s.Config.AuthenticationOptions)) rbacAuthorizer))
urlruntime.Must(oauth.AddToContainer(s.container, imOperator, urlruntime.Must(oauth.AddToContainer(s.container, imOperator,
im.NewTokenOperator( auth.NewTokenOperator(
s.CacheClient, s.CacheClient,
s.Config.AuthenticationOptions), s.Config.AuthenticationOptions),
im.NewPasswordAuthenticator( auth.NewPasswordAuthenticator(
s.KubernetesClient.KubeSphere(), s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister(), s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister(),
s.Config.AuthenticationOptions), 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)) s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container)) urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
urlruntime.Must(networkv1alpha2.AddToContainer(s.container, s.Config.NetworkOptions.WeaveScopeHost)) urlruntime.Must(networkv1alpha2.AddToContainer(s.container, s.Config.NetworkOptions.WeaveScopeHost))
@@ -211,7 +223,7 @@ func (s *APIServer) installKubeSphereAPIs() {
s.KubernetesClient.KubeSphere(), s.KubernetesClient.KubeSphere(),
s.S3Client, s.S3Client,
s.Config.DevopsOptions.Host, s.Config.DevopsOptions.Host,
am.NewOperator(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes()))) amOperator))
urlruntime.Must(devopsv1alpha3.AddToContainer(s.container, urlruntime.Must(devopsv1alpha3.AddToContainer(s.container,
s.DevopsClient, s.DevopsClient,
s.KubernetesClient.Kubernetes(), s.KubernetesClient.Kubernetes(),
@@ -285,7 +297,7 @@ func (s *APIServer) buildHandlerChain(stopCh <-chan struct{}) {
excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*", "/kapis/version"} excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*", "/kapis/version"}
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths) pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
amOperator := am.NewReadOnlyOperator(s.InformerFactory) amOperator := am.NewReadOnlyOperator(s.InformerFactory)
authorizers = unionauthorizer.New(pathAuthorizer, authorizerfactory.NewRBACAuthorizer(amOperator)) authorizers = unionauthorizer.New(pathAuthorizer, rbac.NewRBACAuthorizer(amOperator))
} }
handler = filters.WithAuthorization(handler, authorizers) handler = filters.WithAuthorization(handler, authorizers)
@@ -295,12 +307,16 @@ func (s *APIServer) buildHandlerChain(stopCh <-chan struct{}) {
handler = filters.WithMultipleClusterDispatcher(handler, clusterDispatcher) handler = filters.WithMultipleClusterDispatcher(handler, clusterDispatcher)
} }
loginRecorder := im.NewLoginRecorder(s.KubernetesClient.KubeSphere()) loginRecorder := auth.NewLoginRecorder(s.KubernetesClient.KubeSphere())
// authenticators are unordered // authenticators are unordered
authn := unionauth.New(anonymous.NewAuthenticator(), authn := unionauth.New(anonymous.NewAuthenticator(),
basictoken.New(basic.NewBasicAuthenticator(im.NewPasswordAuthenticator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister(), s.Config.AuthenticationOptions))), basictoken.New(basic.NewBasicAuthenticator(auth.NewPasswordAuthenticator(s.KubernetesClient.KubeSphere(),
bearertoken.New(jwttoken.NewTokenAuthenticator(im.NewTokenOperator(s.CacheClient, s.Config.AuthenticationOptions), s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister()))) s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister(),
handler = filters.WithAuthentication(handler, authn, loginRecorder) 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) handler = filters.WithRequestInfo(handler, requestInfoResolver)
s.Server.Handler = handler s.Server.Handler = handler
} }

View File

@@ -18,10 +18,13 @@ package basic
import ( import (
"context" "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/authenticator"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
"kubesphere.io/kubesphere/pkg/models/iam/im"
) )
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic. // 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, // and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
// because some resources are public accessible. // because some resources are public accessible.
type basicAuthenticator struct { 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{ return &basicAuthenticator{
authenticator: authenticator, authenticator: authenticator,
loginRecorder: loginRecorder,
} }
} }
func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username, password string) (*authenticator.Response, bool, error) { func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
authenticated, provider, err := t.authenticator.Authenticate(username, password)
providedUser, err := t.authenticator.Authenticate(username, password)
if err != nil { 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 nil, false, err
} }
return &authenticator.Response{ return &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: providedUser.GetName(), Name: authenticated.GetName(),
UID: providedUser.GetUID(), Groups: append(authenticated.GetGroups(), user.AllAuthenticated),
Groups: append(providedUser.GetGroups(), user.AllAuthenticated),
}, },
}, true, nil }, true, nil
} }

View File

@@ -18,13 +18,13 @@ package jwttoken
import ( import (
"context" "context"
"k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog" "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" 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. // 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, // and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
// because some resources are public accessible. // because some resources are public accessible.
type tokenAuthenticator struct { type tokenAuthenticator struct {
tokenOperator im.TokenManagementInterface tokenOperator auth.TokenManagementInterface
userLister iamv1alpha2listers.UserLister 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{ return &tokenAuthenticator{
tokenOperator: tokenOperator, tokenOperator: tokenOperator,
userLister: userLister, userLister: userLister,
@@ -51,6 +51,16 @@ func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string
return nil, false, err 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()) dbUser, err := t.userLister.Get(providedUser.GetName())
if err != nil { if err != nil {
return nil, false, err return nil, false, err
@@ -58,8 +68,7 @@ func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string
return &authenticator.Response{ return &authenticator.Response{
User: &user.DefaultInfo{ User: &user.DefaultInfo{
Name: providedUser.GetName(), Name: dbUser.GetName(),
UID: providedUser.GetUID(),
Groups: append(dbUser.Spec.Groups, user.AllAuthenticated), Groups: append(dbUser.Spec.Groups, user.AllAuthenticated),
}, },
}, true, nil }, true, nil

View File

@@ -20,15 +20,19 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/mitchellh/mapstructure"
"io/ioutil" "io/ioutil"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"gopkg.in/yaml.v3"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider" "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" "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 is the application's ID.
ClientID string `json:"clientID" yaml:"clientID"` ClientID string `json:"clientID" yaml:"clientID"`
@@ -39,7 +43,7 @@ type AliyunIDaaS struct {
// URLs. These are constants specific to each server and are // URLs. These are constants specific to each server and are
// often available via site-specific packages, such as // often available via site-specific packages, such as
// google.Endpoint or github.Endpoint. // 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 // RedirectURL is the URL to redirect users going through
// the OAuth flow, after the resource owner's URLs. // the OAuth flow, after the resource owner's URLs.
@@ -49,15 +53,15 @@ type AliyunIDaaS struct {
Scopes []string `json:"scopes" yaml:"scopes"` 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. // endpoint URLs.
type Endpoint struct { type endpoint struct {
AuthURL string `json:"authURL" yaml:"authURL"` AuthURL string `json:"authURL" yaml:"authURL"`
TokenURL string `json:"tokenURL" yaml:"tokenURL"` TokenURL string `json:"tokenURL" yaml:"tokenURL"`
UserInfoURL string `json:"user_info_url" yaml:"userInfoUrl"` UserInfoURL string `json:"user_info_url" yaml:"userInfoUrl"`
} }
type IDaaSIdentity struct { type idaasIdentity struct {
Sub string `json:"sub"` Sub string `json:"sub"`
OuID string `json:"ou_id"` OuID string `json:"ou_id"`
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
@@ -67,72 +71,73 @@ type IDaaSIdentity struct {
Username string `json:"username"` Username string `json:"username"`
} }
type UserInfoResp struct { type userInfoResp struct {
Success bool `json:"success"` Success bool `json:"success"`
Message string `json:"message"` Message string `json:"message"`
Code string `json:"code"` Code string `json:"code"`
IDaaSIdentity IDaaSIdentity `json:"data"` IDaaSIdentity idaasIdentity `json:"data"`
} }
func init() { type idaasProviderFactory struct {
identityprovider.RegisterOAuthProvider(&AliyunIDaaS{})
} }
func (a *AliyunIDaaS) Type() string { func (g *idaasProviderFactory) Type() string {
return "AliyunIDaasProvider" return "AliyunIDaasProvider"
} }
func (a *AliyunIDaaS) Setup(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) { func (g *idaasProviderFactory) Create(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
data, err := yaml.Marshal(options) var idaas aliyunIDaaS
if err != nil { if err := mapstructure.Decode(options, &idaas); err != nil {
return nil, err return nil, err
} }
var provider AliyunIDaaS return &idaas, nil
err = yaml.Unmarshal(data, &provider)
if err != nil {
return nil, err
}
return &provider, nil
} }
func (a IDaaSIdentity) GetName() string { func (a idaasIdentity) GetUserID() string {
return a.Sub
}
func (a idaasIdentity) GetUsername() string {
return a.Username return a.Username
} }
func (a IDaaSIdentity) GetEmail() string { func (a idaasIdentity) GetEmail() string {
return a.Email 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{ config := oauth2.Config{
ClientID: g.ClientID, ClientID: a.ClientID,
ClientSecret: g.ClientSecret, ClientSecret: a.ClientSecret,
Endpoint: oauth2.Endpoint{ Endpoint: oauth2.Endpoint{
AuthURL: g.Endpoint.AuthURL, AuthURL: a.Endpoint.AuthURL,
TokenURL: g.Endpoint.TokenURL, TokenURL: a.Endpoint.TokenURL,
AuthStyle: oauth2.AuthStyleAutoDetect, AuthStyle: oauth2.AuthStyleAutoDetect,
}, },
RedirectURL: g.RedirectURL, RedirectURL: a.RedirectURL,
Scopes: g.Scopes, Scopes: a.Scopes,
} }
token, err := config.Exchange(context.Background(), code) token, err := config.Exchange(context.Background(), code)
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
var UserInfoResp UserInfoResp var UserInfoResp userInfoResp
err = json.Unmarshal(data, &UserInfoResp) err = json.Unmarshal(data, &UserInfoResp)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -0,0 +1,48 @@
/*
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 identityprovider
import (
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
)
var (
builtinGenericProviders = make(map[string]GenericProviderFactory)
)
type GenericProvider interface {
// Authenticate from remote server
Authenticate(username string, password string) (Identity, error)
}
type GenericProviderFactory interface {
// Type unique type of the provider
Type() string
// Apply the dynamic options from kubesphere-config
Create(options *oauth.DynamicOptions) (GenericProvider, error)
}
func CreateGenericProvider(providerType string, options *oauth.DynamicOptions) (GenericProvider, error) {
if factory, ok := builtinGenericProviders[providerType]; ok {
return factory.Create(options)
}
return nil, identityProviderNotFound
}
func RegisterGenericProvider(factory GenericProviderFactory) {
builtinGenericProviders[factory.Type()] = factory
}

View File

@@ -19,8 +19,8 @@ package github
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/mitchellh/mapstructure"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"gopkg.in/yaml.v3"
"io/ioutil" "io/ioutil"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider" "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
@@ -31,7 +31,11 @@ const (
UserInfoURL = "https://api.github.com/user" UserInfoURL = "https://api.github.com/user"
) )
type Github struct { func init() {
identityprovider.RegisterOAuthProvider(&githubProviderFactory{})
}
type github struct {
// ClientID is the application's ID. // ClientID is the application's ID.
ClientID string `json:"clientID" yaml:"clientID"` ClientID string `json:"clientID" yaml:"clientID"`
@@ -41,8 +45,8 @@ type Github struct {
// Endpoint contains the resource server's token endpoint // Endpoint contains the resource server's token endpoint
// URLs. These are constants specific to each server and are // URLs. These are constants specific to each server and are
// often available via site-specific packages, such as // often available via site-specific packages, such as
// google.Endpoint or github.Endpoint. // 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 // RedirectURL is the URL to redirect users going through
// the OAuth flow, after the resource owner's URLs. // the OAuth flow, after the resource owner's URLs.
@@ -52,14 +56,14 @@ type Github struct {
Scopes []string `json:"scopes" yaml:"scopes"` 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. // endpoint URLs.
type Endpoint struct { type endpoint struct {
AuthURL string `json:"authURL" yaml:"authURL"` AuthURL string `json:"authURL" yaml:"authURL"`
TokenURL string `json:"tokenURL" yaml:"tokenURL"` TokenURL string `json:"tokenURL" yaml:"tokenURL"`
} }
type GithubIdentity struct { type githubIdentity struct {
Login string `json:"login"` Login string `json:"login"`
ID int `json:"id"` ID int `json:"id"`
NodeID string `json:"node_id"` NodeID string `json:"node_id"`
@@ -98,36 +102,38 @@ type GithubIdentity struct {
Collaborators int `json:"collaborators"` Collaborators int `json:"collaborators"`
} }
func init() { type githubProviderFactory struct {
identityprovider.RegisterOAuthProvider(&Github{})
} }
func (g *Github) Type() string { func (g *githubProviderFactory) Type() string {
return "GitHubIdentityProvider" return "GitHubIdentityProvider"
} }
func (g *Github) Setup(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) { func (g *githubProviderFactory) Create(options *oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
data, err := yaml.Marshal(options) var github github
if err != nil { if err := mapstructure.Decode(options, &github); err != nil {
return nil, err return nil, err
} }
var provider Github return &github, nil
err = yaml.Unmarshal(data, &provider)
if err != nil {
return nil, err
}
return &provider, nil
} }
func (g GithubIdentity) GetName() string { func (g githubIdentity) GetUserID() string {
return g.Login return g.Login
} }
func (g GithubIdentity) GetEmail() string { func (g githubIdentity) GetUsername() string {
return g.Login
}
func (g githubIdentity) GetEmail() string {
return g.Email 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{ config := oauth2.Config{
ClientID: g.ClientID, ClientID: g.ClientID,
ClientSecret: g.ClientSecret, ClientSecret: g.ClientSecret,
@@ -141,27 +147,23 @@ func (g *Github) IdentityExchange(code string) (identityprovider.Identity, error
} }
token, err := config.Exchange(context.Background(), code) token, err := config.Exchange(context.Background(), code)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp, err := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(token)).Get(UserInfoURL) resp, err := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(token)).Get(UserInfoURL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
var githubIdentity GithubIdentity var githubIdentity githubIdentity
err = json.Unmarshal(data, &githubIdentity) err = json.Unmarshal(data, &githubIdentity)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -17,6 +17,12 @@ limitations under the License.
package identityprovider package identityprovider
type Identity interface { type Identity interface {
GetName() string // required
GetUserID() string
// optional
GetUsername() string
// optional
GetDisplayName() string
// optional
GetEmail() string GetEmail() string
} }

View File

@@ -1,22 +1,20 @@
/* /*
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"); http://www.apache.org/licenses/LICENSE-2.0
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 identityprovider package ldap
import ( import (
"crypto/tls" "crypto/tls"
@@ -26,24 +24,23 @@ import (
"github.com/go-ldap/ldap" "github.com/go-ldap/ldap"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"io/ioutil" "io/ioutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog" "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/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/constants"
"time" "time"
) )
const ( const (
LdapIdentityProvider = "LDAPIdentityProvider" ldapIdentityProvider = "LDAPIdentityProvider"
defaultReadTimeout = 15000 defaultReadTimeout = 15000
) )
type LdapProvider interface { func init() {
Authenticate(username string, password string) (*iamv1alpha2.User, error) identityprovider.RegisterGenericProvider(&ldapProviderFactory{})
} }
type ldapOptions struct { type ldapProvider struct {
// Host and optional port of the LDAP server in the form "host:port". // 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 // If the port is not supplied, 389 for insecure or StartTLS connections, 636
Host string `json:"host,omitempty" yaml:"managerDN"` Host string `json:"host,omitempty" yaml:"managerDN"`
@@ -73,105 +70,125 @@ type ldapOptions struct {
UserMemberAttribute string `json:"userMemberAttribute,omitempty" yaml:"userMemberAttribute"` UserMemberAttribute string `json:"userMemberAttribute,omitempty" yaml:"userMemberAttribute"`
// Attribute on a group object storing the information for primary group membership. // Attribute on a group object storing the information for primary group membership.
GroupMemberAttribute string `json:"groupMemberAttribute,omitempty" yaml:"groupMemberAttribute"` 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. // 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"` LoginAttribute string `json:"loginAttribute" yaml:"loginAttribute"`
MailAttribute string `json:"mailAttribute" yaml:"mailAttribute"` MailAttribute string `json:"mailAttribute" yaml:"mailAttribute"`
DisplayNameAttribute string `json:"displayNameAttribute" yaml:"displayNameAttribute"` DisplayNameAttribute string `json:"displayNameAttribute" yaml:"displayNameAttribute"`
} }
type ldapProvider struct { type ldapProviderFactory struct {
options ldapOptions
} }
func NewLdapProvider(options *oauth.DynamicOptions) (LdapProvider, error) { func (l *ldapProviderFactory) Type() string {
var ldapOptions ldapOptions return ldapIdentityProvider
if err := mapstructure.Decode(options, &ldapOptions); err != nil { }
func (l *ldapProviderFactory) Create(options *oauth.DynamicOptions) (identityprovider.GenericProvider, error) {
var ldapProvider ldapProvider
if err := mapstructure.Decode(options, &ldapProvider); err != nil {
return nil, err return nil, err
} }
if ldapOptions.ReadTimeout <= 0 { if ldapProvider.ReadTimeout <= 0 {
ldapOptions.ReadTimeout = defaultReadTimeout ldapProvider.ReadTimeout = defaultReadTimeout
} }
return &ldapProvider{options: ldapOptions}, nil return &ldapProvider, nil
} }
func (l ldapProvider) Authenticate(username string, password string) (*iamv1alpha2.User, error) { type ldapIdentity struct {
Username string
Email string
DisplayName string
}
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() conn, err := l.newConn()
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
conn.SetTimeout(time.Duration(l.options.ReadTimeout) * time.Millisecond) conn.SetTimeout(time.Duration(l.ReadTimeout) * time.Millisecond)
defer conn.Close() defer conn.Close()
err = conn.Bind(l.options.ManagerDN, l.options.ManagerPassword) err = conn.Bind(l.ManagerDN, l.ManagerPassword)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, 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{ result, err := conn.Search(&ldap.SearchRequest{
BaseDN: l.options.UserSearchBase, BaseDN: l.UserSearchBase,
Scope: ldap.ScopeWholeSubtree, Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases, DerefAliases: ldap.NeverDerefAliases,
SizeLimit: 1, SizeLimit: 1,
TimeLimit: 0, TimeLimit: 0,
TypesOnly: false, TypesOnly: false,
Filter: filter, Filter: filter,
Attributes: []string{l.options.LoginAttribute, l.options.MailAttribute, l.options.DisplayNameAttribute}, Attributes: []string{l.LoginAttribute, l.MailAttribute, l.DisplayNameAttribute},
}) })
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
if len(result.Entries) == 1 { if len(result.Entries) != 1 {
entry := result.Entries[0] return nil, errors.NewUnauthorized("incorrect password")
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
} }
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) { func (l *ldapProvider) newConn() (*ldap.Conn, error) {
if !l.options.StartTLS { if !l.StartTLS {
return ldap.Dial("tcp", l.options.Host) return ldap.Dial("tcp", l.Host)
} }
tlsConfig := tls.Config{} tlsConfig := tls.Config{}
if l.options.InsecureSkipVerify { if l.InsecureSkipVerify {
tlsConfig.InsecureSkipVerify = true tlsConfig.InsecureSkipVerify = true
} }
tlsConfig.RootCAs = x509.NewCertPool() tlsConfig.RootCAs = x509.NewCertPool()
var caCert []byte var caCert []byte
var err error var err error
// Load CA cert // Load CA cert
if l.options.RootCA != "" { if l.RootCA != "" {
if caCert, err = ioutil.ReadFile(l.options.RootCA); err != nil { if caCert, err = ioutil.ReadFile(l.RootCA); err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
} }
if l.options.RootCAData != "" { if l.RootCAData != "" {
if caCert, err = base64.StdEncoding.DecodeString(l.options.RootCAData); err != nil { if caCert, err = base64.StdEncoding.DecodeString(l.RootCAData); err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
@@ -179,5 +196,5 @@ func (l *ldapProvider) newConn() (*ldap.Conn, error) {
if caCert != nil { if caCert != nil {
tlsConfig.RootCAs.AppendCertsFromPEM(caCert) tlsConfig.RootCAs.AppendCertsFromPEM(caCert)
} }
return ldap.DialTLS("tcp", l.options.Host, &tlsConfig) return ldap.DialTLS("tcp", l.Host, &tlsConfig)
} }

View File

@@ -1,22 +1,20 @@
/* /*
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"); http://www.apache.org/licenses/LICENSE-2.0
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 identityprovider package ldap
import ( import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@@ -42,12 +40,11 @@ mailAttribute: mail
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
provider, err := NewLdapProvider(&dynamicOptions) got, err := new(ldapProviderFactory).Create(&dynamicOptions)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
got := provider.(*ldapProvider).options expected := &ldapProvider{
expected := ldapOptions{
Host: "test.sn.mynetname.net:389", Host: "test.sn.mynetname.net:389",
StartTLS: false, StartTLS: false,
InsecureSkipVerify: false, InsecureSkipVerify: false,
@@ -81,14 +78,14 @@ func TestLdapProvider_Authenticate(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
var dynamicOptions oauth.DynamicOptions var dynamicOptions oauth.DynamicOptions
if err := yaml.Unmarshal(options, &dynamicOptions); err != nil { if err = yaml.Unmarshal(options, &dynamicOptions); err != nil {
t.Fatal(err) t.Fatal(err)
} }
provider, err := NewLdapProvider(&dynamicOptions) ldapProvider, err := new(ldapProviderFactory).Create(&dynamicOptions)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := provider.Authenticate("test", "test"); err != nil { if _, err = ldapProvider.Authenticate("test", "test"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }

View File

@@ -1,21 +1,18 @@
/* /*
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"); http://www.apache.org/licenses/LICENSE-2.0
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 identityprovider package identityprovider
import ( import (
@@ -24,23 +21,29 @@ import (
) )
var ( var (
oauthProviders = make(map[string]OAuthProvider, 0) builtinOAuthProviders = make(map[string]OAuthProviderFactory)
ErrorIdentityProviderNotFound = errors.New("the identity provider was not found") identityProviderNotFound = errors.New("identity provider not found")
) )
type OAuthProvider interface { type OAuthProvider interface {
Type() string // IdentityExchange exchange identity from remote server
Setup(options *oauth.DynamicOptions) (OAuthProvider, error)
IdentityExchange(code string) (Identity, error) IdentityExchange(code string) (Identity, error)
} }
func GetOAuthProvider(providerType string, options *oauth.DynamicOptions) (OAuthProvider, error) { type OAuthProviderFactory interface {
if provider, ok := oauthProviders[providerType]; ok { // Type unique type of the provider
return provider.Setup(options) Type() string
} // Apply the dynamic options from kubesphere-config
return nil, ErrorIdentityProviderNotFound Create(options *oauth.DynamicOptions) (OAuthProvider, error)
} }
func RegisterOAuthProvider(provider OAuthProvider) { func CreateOAuthProvider(providerType string, options *oauth.DynamicOptions) (OAuthProvider, error) {
oauthProviders[provider.Type()] = provider if provider, ok := builtinOAuthProviders[providerType]; ok {
return provider.Create(options)
}
return nil, identityProviderNotFound
}
func RegisterOAuthProvider(factory OAuthProviderFactory) {
builtinOAuthProviders[factory.Type()] = factory
} }

View File

@@ -35,12 +35,13 @@ const (
// GrantHandlerDeny auto-denies client authorization grant requests // GrantHandlerDeny auto-denies client authorization grant requests
GrantHandlerDeny GrantHandlerType = "deny" GrantHandlerDeny GrantHandlerType = "deny"
// MappingMethodAuto The default value.The user will automatically create and mapping when login successful. // 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" MappingMethodAuto MappingMethod = "auto"
// MappingMethodLookup Looks up an existing identity, user identity mapping, and user, but does not automatically // 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. // provision users or identities. Using this method requires you to manually provision users.
MappingMethodLookup MappingMethod = "lookup" MappingMethodLookup MappingMethod = "lookup"
// MappingMethodMixed A user entity can be mapped with multiple identifyProvider. // MappingMethodMixed A user entity can be mapped with multiple identifyProvider.
// not supported yet.
MappingMethodMixed MappingMethod = "mixed" MappingMethodMixed MappingMethod = "mixed"
) )

View File

@@ -21,6 +21,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
_ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/aliyunidaas" _ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/aliyunidaas"
_ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/github" _ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/github"
_ "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider/ldap"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"time" "time"
) )
@@ -66,7 +67,6 @@ func (options *AuthenticationOptions) Validate() []error {
if len(options.JwtSecret) == 0 { if len(options.JwtSecret) == 0 {
errs = append(errs, fmt.Errorf("jwt secret is empty")) errs = append(errs, fmt.Errorf("jwt secret is empty"))
} }
return errs return errs
} }

View File

@@ -29,9 +29,10 @@ const (
) )
type Claims struct { type Claims struct {
Username string `json:"username"` Username string `json:"username"`
UID string `json:"uid"` Groups []string `json:"groups,omitempty"`
TokenType TokenType `json:"token_type"` Extra map[string][]string `json:"extra,omitempty"`
TokenType TokenType `json:"token_type"`
// Currently, we are not using any field in jwt.StandardClaims // Currently, we are not using any field in jwt.StandardClaims
jwt.StandardClaims jwt.StandardClaims
} }
@@ -51,7 +52,7 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (user.Info, TokenType, error
klog.Error(err) klog.Error(err)
return nil, "", 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) { 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 notBefore := issueAt
clm := &Claims{ clm := &Claims{
Username: user.GetName(), Username: user.GetName(),
UID: user.GetUID(), Groups: user.GetGroups(),
Extra: user.GetExtra(),
TokenType: tokenType, TokenType: tokenType,
StandardClaims: jwt.StandardClaims{ StandardClaims: jwt.StandardClaims{
IssuedAt: issueAt, IssuedAt: issueAt,

View File

@@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package v1 // Following code copied from k8s.io/kubernetes/pkg/apis/rbac/v1 to avoid import collision
package rbac
import ( import (
"fmt" "fmt"

View File

@@ -16,7 +16,7 @@ limitations under the License.
// NOTE: This file is copied from k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac. // NOTE: This file is copied from k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac.
package authorizerfactory package rbac
import ( import (
"bytes" "bytes"
@@ -36,7 +36,6 @@ import (
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
rbacv1helpers "kubesphere.io/kubesphere/pkg/api/rbac/v1"
) )
const ( const (
@@ -159,14 +158,14 @@ func ruleAllows(requestAttributes authorizer.Attributes, rule *rbacv1.PolicyRule
combinedResource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource() combinedResource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource()
} }
return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) && return VerbMatches(rule, requestAttributes.GetVerb()) &&
rbacv1helpers.APIGroupMatches(rule, requestAttributes.GetAPIGroup()) && APIGroupMatches(rule, requestAttributes.GetAPIGroup()) &&
rbacv1helpers.ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) && ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) &&
rbacv1helpers.ResourceNameMatches(rule, requestAttributes.GetName()) ResourceNameMatches(rule, requestAttributes.GetName())
} }
return rbacv1helpers.VerbMatches(rule, requestAttributes.GetVerb()) && return VerbMatches(rule, requestAttributes.GetVerb()) &&
rbacv1helpers.NonResourceURLMatches(rule, requestAttributes.GetPath()) NonResourceURLMatches(rule, requestAttributes.GetPath())
} }
func regoPolicyAllows(requestAttributes authorizer.Attributes, regoPolicy string) bool { func regoPolicyAllows(requestAttributes authorizer.Attributes, regoPolicy string) bool {

View File

@@ -1,20 +1,22 @@
/* /*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Copyright 2020 The KubeSphere Authors.
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 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 ( import (
"errors" "errors"

View File

@@ -26,26 +26,23 @@ import (
"k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/klog" "k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/request" "kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/models/iam/im"
"net/http" "net/http"
"strings"
) )
// WithAuthentication installs authentication handler to handler chain. // WithAuthentication installs authentication handler to handler chain.
// The following part is a little bit ugly, WithAuthentication also logs user failed login attempt // 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 // 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 { func WithAuthentication(handler http.Handler, authRequest authenticator.Request) http.Handler {
if auth == nil { if authRequest == nil {
klog.Warningf("Authentication is disabled") klog.Warningf("Authentication is disabled")
return handler return handler
} }
s := serializer.NewCodecFactory(runtime.NewScheme()).WithoutConversion() s := serializer.NewCodecFactory(runtime.NewScheme()).WithoutConversion()
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
resp, ok, err := auth.AuthenticateRequest(req) resp, ok, err := authRequest.AuthenticateRequest(req)
username, _, usingBasicAuth := req.BasicAuth() _, _, usingBasicAuth := req.BasicAuth()
defer func() { defer func() {
// if we authenticated successfully, go ahead and remove the bearer token so that no one // 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 || !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() ctx := req.Context()
requestInfo, found := request.RequestInfoFrom(ctx) requestInfo, found := request.RequestInfoFrom(ctx)
if !found { if !found {
responsewriters.InternalError(w, req, errors.New("no RequestInfo found in the context")) responsewriters.InternalError(w, req, errors.New("no RequestInfo found in the context"))
return return
} }
gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
if err != nil && err.Error() == im.AuthRateLimitExceeded.Error() { responsewriters.ErrorNegotiated(apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)), s, gv, w, req)
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)
}
return 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)) req = req.WithContext(request.WithUser(req.Context(), resp.User))
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
}) })

View File

@@ -31,6 +31,7 @@ import (
"k8s.io/klog" "k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/constants"
netutils "kubesphere.io/kubesphere/pkg/utils/net"
"net/http" "net/http"
"strings" "strings"
@@ -74,6 +75,12 @@ type RequestInfo struct {
// Scope of requested resource. // Scope of requested resource.
ResourceScope string ResourceScope string
// Source IP
SourceIP string
// User agent
UserAgent string
} }
type RequestInfoFactory struct { type RequestInfoFactory struct {
@@ -119,6 +126,8 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
}, },
Workspace: api.WorkspaceNone, Workspace: api.WorkspaceNone,
Cluster: api.ClusterNone, Cluster: api.ClusterNone,
SourceIP: netutils.GetRequestIP(req),
UserAgent: req.UserAgent(),
} }
defer func() { defer func() {

View File

@@ -0,0 +1,162 @@
/*
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
}

View File

@@ -0,0 +1,265 @@
/*
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))
}

View File

@@ -1,224 +0,0 @@
/*
*
* 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
}

View File

@@ -19,8 +19,8 @@ package user
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"kubesphere.io/kubesphere/pkg/controller/utils/controller"
"reflect" "reflect"
"strconv"
"time" "time"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@@ -31,7 +31,6 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
corev1informers "k8s.io/client-go/informers/core/v1" corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
@@ -62,46 +61,36 @@ const (
// is synced successfully // is synced successfully
messageResourceSynced = "User synced successfully" messageResourceSynced = "User synced successfully"
controllerName = "user-controller" controllerName = "user-controller"
// user finalizer // user finalizer
finalizer = "finalizers.kubesphere.io/users" finalizer = "finalizers.kubesphere.io/users"
) )
type Controller struct { type userController struct {
k8sClient kubernetes.Interface controller.BaseController
ksClient kubesphere.Interface k8sClient kubernetes.Interface
kubeconfig kubeconfig.Interface ksClient kubesphere.Interface
userLister iamv1alpha2listers.UserLister kubeconfig kubeconfig.Interface
userSynced cache.InformerSynced userLister iamv1alpha2listers.UserLister
loginRecordLister iamv1alpha2listers.LoginRecordLister loginRecordLister iamv1alpha2listers.LoginRecordLister
loginRecordSynced cache.InformerSynced fedUserCache cache.Store
cmSynced cache.InformerSynced ldapClient ldapclient.Interface
fedUserCache cache.Store devopsClient devops.Interface
fedUserController cache.Controller
ldapClient ldapclient.Interface
devopsClient devops.Interface
// 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
authenticationOptions *authoptions.AuthenticationOptions authenticationOptions *authoptions.AuthenticationOptions
multiClusterEnabled bool multiClusterEnabled bool
// recorder is an event recorder for recording Event resources to the
// Kubernetes API.
recorder record.EventRecorder
} }
func NewUserController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface, func NewUserController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface, config *rest.Config,
config *rest.Config, userInformer iamv1alpha2informers.UserInformer, userInformer iamv1alpha2informers.UserInformer,
fedUserCache cache.Store, fedUserController cache.Controller,
loginRecordInformer iamv1alpha2informers.LoginRecordInformer, loginRecordInformer iamv1alpha2informers.LoginRecordInformer,
fedUserCache cache.Store, fedUserController cache.Controller,
configMapInformer corev1informers.ConfigMapInformer, configMapInformer corev1informers.ConfigMapInformer,
ldapClient ldapclient.Interface, ldapClient ldapclient.Interface,
devopsClient devops.Interface, devopsClient devops.Interface,
authenticationOptions *authoptions.AuthenticationOptions, authenticationOptions *authoptions.AuthenticationOptions,
multiClusterEnabled bool) *Controller { multiClusterEnabled bool) *userController {
utilruntime.Must(kubespherescheme.AddToScheme(scheme.Scheme)) utilruntime.Must(kubespherescheme.AddToScheme(scheme.Scheme))
@@ -113,152 +102,48 @@ func NewUserController(k8sClient kubernetes.Interface, ksClient kubesphere.Inter
if config != nil { if config != nil {
kubeconfigOperator = kubeconfig.NewOperator(k8sClient, configMapInformer, config) kubeconfigOperator = kubeconfig.NewOperator(k8sClient, configMapInformer, config)
} }
ctl := &Controller{ ctl := &userController{
BaseController: controller.BaseController{
Workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "User"),
Synced: []cache.InformerSynced{
userInformer.Informer().HasSynced,
configMapInformer.Informer().HasSynced,
loginRecordInformer.Informer().HasSynced,
},
Name: controllerName,
},
k8sClient: k8sClient, k8sClient: k8sClient,
ksClient: ksClient, ksClient: ksClient,
kubeconfig: kubeconfigOperator, kubeconfig: kubeconfigOperator,
userLister: userInformer.Lister(), userLister: userInformer.Lister(),
userSynced: userInformer.Informer().HasSynced,
loginRecordLister: loginRecordInformer.Lister(), loginRecordLister: loginRecordInformer.Lister(),
loginRecordSynced: loginRecordInformer.Informer().HasSynced,
cmSynced: configMapInformer.Informer().HasSynced,
fedUserCache: fedUserCache, fedUserCache: fedUserCache,
fedUserController: fedUserController,
ldapClient: ldapClient, ldapClient: ldapClient,
devopsClient: devopsClient, devopsClient: devopsClient,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Users"),
recorder: recorder, recorder: recorder,
multiClusterEnabled: multiClusterEnabled, multiClusterEnabled: multiClusterEnabled,
authenticationOptions: authenticationOptions, authenticationOptions: authenticationOptions,
} }
if multiClusterEnabled {
ctl.Synced = append(ctl.Synced, fedUserController.HasSynced)
}
ctl.Handler = ctl.reconcile
klog.Info("Setting up event handlers") klog.Info("Setting up event handlers")
userInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ userInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ctl.enqueueUser, AddFunc: ctl.Enqueue,
UpdateFunc: func(old, new interface{}) { UpdateFunc: func(old, new interface{}) {
ctl.enqueueUser(new) ctl.Enqueue(new)
},
DeleteFunc: ctl.enqueueUser,
})
loginRecordInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(new interface{}) {
if err := ctl.enqueueLogin(new); err != nil {
klog.Errorf("Failed to enqueue login object, error: %v", err)
}
}, },
DeleteFunc: ctl.Enqueue,
}) })
return ctl return ctl
} }
func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error { func (c *userController) Start(stopCh <-chan struct{}) error {
defer utilruntime.HandleCrash() return c.Run(5, stopCh)
defer c.workqueue.ShutDown()
// Start the informer factories to begin populating the informer caches
klog.Info("Starting User controller")
// Wait for the caches to be synced before starting workers
klog.Info("Waiting for informer caches to sync")
synced := make([]cache.InformerSynced, 0)
synced = append(synced, c.userSynced, c.loginRecordSynced, c.cmSynced)
if c.multiClusterEnabled {
synced = append(synced, c.fedUserController.HasSynced)
}
if ok := cache.WaitForCacheSync(stopCh, synced...); !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)
}
klog.Info("Started workers")
<-stopCh
klog.Info("Shutting down workers")
return nil
} }
func (c *Controller) enqueueUser(obj interface{}) { func (c *userController) reconcile(key string) error {
var key string
var err error
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
utilruntime.HandleError(err)
return
}
c.workqueue.Add(key)
}
func (c *Controller) runWorker() {
for c.processNextWorkItem() {
}
}
func (c *Controller) processNextWorkItem() bool {
obj, shutdown := c.workqueue.Get()
if shutdown {
return false
}
err := func(obj interface{}) error {
defer c.workqueue.Done(obj)
var key string
var ok bool
if key, ok = obj.(string); !ok {
c.workqueue.Forget(obj)
utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
return nil
}
if err := c.reconcile(key); err != nil {
c.workqueue.AddRateLimited(key)
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
}
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
}
// enqueueLogin accepts a login object and set user lastLoginTime field
func (c *Controller) enqueueLogin(object interface{}) error {
login := object.(*iamv1alpha2.LoginRecord)
username, ok := login.Labels[iamv1alpha2.UserReferenceLabel]
if !ok || len(username) == 0 {
return fmt.Errorf("login doesn't belong to any user")
}
user, err := c.userLister.Get(username)
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("user %s doesn't exist any more, login record will be deleted later", username)
}
return err
}
if user.Status.LastLoginTime == nil || user.Status.LastLoginTime.Before(&login.CreationTimestamp) {
user.Status.LastLoginTime = &login.CreationTimestamp
user, err = c.ksClient.IamV1alpha2().Users().Update(user)
return err
}
return nil
}
func (c *Controller) reconcile(key string) error {
// Get the user with this name // Get the user with this name
user, err := c.userLister.Get(key) user, err := c.userLister.Get(key)
if err != nil { if err != nil {
@@ -307,7 +192,7 @@ func (c *Controller) reconcile(key string) error {
if c.devopsClient != nil { if c.devopsClient != nil {
// unassign jenkins role, unassign multiple times is allowed // unassign jenkins role, unassign multiple times is allowed
if err := c.unassignDevOpsAdminRole(user); err != nil { if err = c.unassignDevOpsAdminRole(user); err != nil {
klog.Error(err) klog.Error(err)
return err return err
} }
@@ -340,7 +225,7 @@ func (c *Controller) reconcile(key string) error {
} }
} }
if user, err = c.ensurePasswordIsEncrypted(user); err != nil { if user, err = c.encryptPassword(user); err != nil {
klog.Error(err) klog.Error(err)
return err return err
} }
@@ -361,7 +246,7 @@ func (c *Controller) reconcile(key string) error {
if c.devopsClient != nil { if c.devopsClient != nil {
// assign jenkins role after user create, assign multiple times is allowed // assign jenkins role after user create, assign multiple times is allowed
// used as logged-in users can do anything // used as logged-in users can do anything
if err := c.assignDevOpsAdminRole(user); err != nil { if err = c.assignDevOpsAdminRole(user); err != nil {
klog.Error(err) klog.Error(err)
return err return err
} }
@@ -379,14 +264,9 @@ func (c *Controller) reconcile(key string) error {
return nil return nil
} }
func (c *Controller) Start(stopCh <-chan struct{}) error { func (c *userController) encryptPassword(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
return c.Run(5, stopCh) // password is not empty and not encrypted
} if user.Spec.EncryptedPassword != "" && !isEncrypted(user.Spec.EncryptedPassword) {
func (c *Controller) ensurePasswordIsEncrypted(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
encrypted := user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] == "true"
// password is not encrypted
if !encrypted {
password, err := encrypt(user.Spec.EncryptedPassword) password, err := encrypt(user.Spec.EncryptedPassword)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
@@ -395,22 +275,16 @@ func (c *Controller) ensurePasswordIsEncrypted(user *iamv1alpha2.User) (*iamv1al
user = user.DeepCopy() user = user.DeepCopy()
user.Spec.EncryptedPassword = password user.Spec.EncryptedPassword = password
if user.Annotations == nil { if user.Annotations == nil {
user.Annotations = make(map[string]string, 0) user.Annotations = make(map[string]string)
} }
// ensure plain text password won't be kept anywhere // ensure plain text password won't be kept anywhere
delete(user.Annotations, corev1.LastAppliedConfigAnnotation) delete(user.Annotations, corev1.LastAppliedConfigAnnotation)
user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] = "true"
user.Status = iamv1alpha2.UserStatus{
State: iamv1alpha2.UserActive,
LastTransitionTime: &metav1.Time{Time: time.Now()},
}
return c.ksClient.IamV1alpha2().Users().Update(user) return c.ksClient.IamV1alpha2().Users().Update(user)
} }
return user, nil return user, nil
} }
func (c *Controller) ensureNotControlledByKubefed(user *iamv1alpha2.User) error { func (c *userController) ensureNotControlledByKubefed(user *iamv1alpha2.User) error {
if user.Labels[constants.KubefedManagedLabel] != "false" { if user.Labels[constants.KubefedManagedLabel] != "false" {
if user.Labels == nil { if user.Labels == nil {
user.Labels = make(map[string]string, 0) user.Labels = make(map[string]string, 0)
@@ -425,7 +299,7 @@ func (c *Controller) ensureNotControlledByKubefed(user *iamv1alpha2.User) error
return nil return nil
} }
func (c *Controller) multiClusterSync(user *iamv1alpha2.User) error { func (c *userController) multiClusterSync(user *iamv1alpha2.User) error {
if err := c.ensureNotControlledByKubefed(user); err != nil { if err := c.ensureNotControlledByKubefed(user); err != nil {
klog.Error(err) klog.Error(err)
@@ -458,7 +332,7 @@ func (c *Controller) multiClusterSync(user *iamv1alpha2.User) error {
return nil return nil
} }
func (c *Controller) createFederatedUser(user *iamv1alpha2.User) error { func (c *userController) createFederatedUser(user *iamv1alpha2.User) error {
federatedUser := &iamv1alpha2.FederatedUser{ federatedUser := &iamv1alpha2.FederatedUser{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: iamv1alpha2.FedUserKind, Kind: iamv1alpha2.FedUserKind,
@@ -506,14 +380,13 @@ func (c *Controller) createFederatedUser(user *iamv1alpha2.User) error {
return nil return nil
} }
func (c *Controller) updateFederatedUser(fedUser *iamv1alpha2.FederatedUser) error { func (c *userController) updateFederatedUser(fedUser *iamv1alpha2.FederatedUser) error {
data, err := json.Marshal(fedUser) data, err := json.Marshal(fedUser)
if err != nil { if err != nil {
return err return err
} }
cli := c.k8sClient.(*kubernetes.Clientset) cli := c.k8sClient.(*kubernetes.Clientset)
err = cli.RESTClient().Put(). err = cli.RESTClient().Put().
AbsPath(fmt.Sprintf("/apis/%s/%s/%s/%s", iamv1alpha2.FedUserResource.Group, AbsPath(fmt.Sprintf("/apis/%s/%s/%s/%s", iamv1alpha2.FedUserResource.Group,
iamv1alpha2.FedUserResource.Version, iamv1alpha2.FedUserResource.Name, fedUser.Name)). iamv1alpha2.FedUserResource.Version, iamv1alpha2.FedUserResource.Name, fedUser.Name)).
@@ -529,7 +402,7 @@ func (c *Controller) updateFederatedUser(fedUser *iamv1alpha2.FederatedUser) err
return nil return nil
} }
func (c *Controller) assignDevOpsAdminRole(user *iamv1alpha2.User) error { func (c *userController) assignDevOpsAdminRole(user *iamv1alpha2.User) error {
if err := c.devopsClient.AssignGlobalRole(modelsdevops.JenkinsAdminRoleName, user.Name); err != nil { if err := c.devopsClient.AssignGlobalRole(modelsdevops.JenkinsAdminRoleName, user.Name); err != nil {
klog.Errorf("%+v", err) klog.Errorf("%+v", err)
return err return err
@@ -537,7 +410,7 @@ func (c *Controller) assignDevOpsAdminRole(user *iamv1alpha2.User) error {
return nil return nil
} }
func (c *Controller) unassignDevOpsAdminRole(user *iamv1alpha2.User) error { func (c *userController) unassignDevOpsAdminRole(user *iamv1alpha2.User) error {
if err := c.devopsClient.UnAssignGlobalRole(modelsdevops.JenkinsAdminRoleName, user.Name); err != nil { if err := c.devopsClient.UnAssignGlobalRole(modelsdevops.JenkinsAdminRoleName, user.Name); err != nil {
klog.Errorf("%+v", err) klog.Errorf("%+v", err)
return err return err
@@ -545,9 +418,8 @@ func (c *Controller) unassignDevOpsAdminRole(user *iamv1alpha2.User) error {
return nil return nil
} }
func (c *Controller) ldapSync(user *iamv1alpha2.User) error { func (c *userController) ldapSync(user *iamv1alpha2.User) error {
encrypted, _ := strconv.ParseBool(user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation]) if isEncrypted(user.Spec.EncryptedPassword) {
if encrypted {
return nil return nil
} }
_, err := c.ldapClient.Get(user.Name) _, err := c.ldapClient.Get(user.Name)
@@ -564,14 +436,12 @@ func (c *Controller) ldapSync(user *iamv1alpha2.User) error {
} }
} }
func (c *Controller) deleteGroupBindings(user *iamv1alpha2.User) error { func (c *userController) deleteGroupBindings(user *iamv1alpha2.User) error {
// Groupbindings that created by kubeshpere will be deleted directly. // Groupbindings that created by kubeshpere will be deleted directly.
listOptions := metav1.ListOptions{ listOptions := metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(labels.Set{iamv1alpha2.UserReferenceLabel: user.Name}).String(), LabelSelector: labels.SelectorFromSet(labels.Set{iamv1alpha2.UserReferenceLabel: user.Name}).String(),
} }
deleteOptions := metav1.NewDeleteOptions(0) deleteOptions := metav1.NewDeleteOptions(0)
if err := c.ksClient.IamV1alpha2().GroupBindings(). if err := c.ksClient.IamV1alpha2().GroupBindings().
DeleteCollection(deleteOptions, listOptions); err != nil { DeleteCollection(deleteOptions, listOptions); err != nil {
klog.Error(err) klog.Error(err)
@@ -580,12 +450,11 @@ func (c *Controller) deleteGroupBindings(user *iamv1alpha2.User) error {
return nil return nil
} }
func (c *Controller) deleteRoleBindings(user *iamv1alpha2.User) error { func (c *userController) deleteRoleBindings(user *iamv1alpha2.User) error {
listOptions := metav1.ListOptions{ listOptions := metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(labels.Set{iamv1alpha2.UserReferenceLabel: user.Name}).String(), LabelSelector: labels.SelectorFromSet(labels.Set{iamv1alpha2.UserReferenceLabel: user.Name}).String(),
} }
deleteOptions := metav1.NewDeleteOptions(0) deleteOptions := metav1.NewDeleteOptions(0)
if err := c.ksClient.IamV1alpha2().GlobalRoleBindings(). if err := c.ksClient.IamV1alpha2().GlobalRoleBindings().
DeleteCollection(deleteOptions, listOptions); err != nil { DeleteCollection(deleteOptions, listOptions); err != nil {
klog.Error(err) klog.Error(err)
@@ -619,7 +488,7 @@ func (c *Controller) deleteRoleBindings(user *iamv1alpha2.User) error {
return nil return nil
} }
func (c *Controller) deleteLoginRecords(user *iamv1alpha2.User) error { func (c *userController) deleteLoginRecords(user *iamv1alpha2.User) error {
listOptions := metav1.ListOptions{ listOptions := metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(labels.Set{iamv1alpha2.UserReferenceLabel: user.Name}).String(), LabelSelector: labels.SelectorFromSet(labels.Set{iamv1alpha2.UserReferenceLabel: user.Name}).String(),
} }
@@ -634,28 +503,60 @@ func (c *Controller) deleteLoginRecords(user *iamv1alpha2.User) error {
} }
// syncUserStatus will reconcile user state based on user login records // syncUserStatus will reconcile user state based on user login records
func (c *Controller) syncUserStatus(user *iamv1alpha2.User) (*iamv1alpha2.User, error) { func (c *userController) syncUserStatus(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
// disabled user, nothing to do // disabled user, nothing to do
if user == nil || (user.Status.State == iamv1alpha2.UserDisabled) { if user.Status.State != nil && *user.Status.State == iamv1alpha2.UserDisabled {
return user, nil return user, nil
} }
// mapped user from other identity provider always active until disabled
if user.Spec.EncryptedPassword == "" &&
user.Labels[iamv1alpha2.IdentifyProviderLabel] != "" &&
(user.Status.State == nil || *user.Status.State != iamv1alpha2.UserActive) {
expected := user.DeepCopy()
active := iamv1alpha2.UserActive
expected.Status = iamv1alpha2.UserStatus{
State: &active,
LastTransitionTime: &metav1.Time{Time: time.Now()},
}
return c.ksClient.IamV1alpha2().Users().UpdateStatus(expected)
}
// becomes inactive after setting a blank password
if user.Spec.EncryptedPassword == "" &&
user.Labels[iamv1alpha2.IdentifyProviderLabel] == "" {
expected := user.DeepCopy()
expected.Status = iamv1alpha2.UserStatus{
State: nil,
LastTransitionTime: &metav1.Time{Time: time.Now()},
}
return c.ksClient.IamV1alpha2().Users().UpdateStatus(expected)
}
// becomes active after password encrypted
if isEncrypted(user.Spec.EncryptedPassword) &&
user.Status.State == nil {
expected := user.DeepCopy()
active := iamv1alpha2.UserActive
expected.Status = iamv1alpha2.UserStatus{
State: &active,
LastTransitionTime: &metav1.Time{Time: time.Now()},
}
return c.ksClient.IamV1alpha2().Users().UpdateStatus(expected)
}
// blocked user, check if need to unblock user // blocked user, check if need to unblock user
if user.Status.State == iamv1alpha2.UserAuthLimitExceeded { if user.Status.State != nil && *user.Status.State == iamv1alpha2.UserAuthLimitExceeded {
if user.Status.LastTransitionTime != nil && if user.Status.LastTransitionTime != nil &&
user.Status.LastTransitionTime.Add(c.authenticationOptions.AuthenticateRateLimiterDuration).Before(time.Now()) { user.Status.LastTransitionTime.Add(c.authenticationOptions.AuthenticateRateLimiterDuration).Before(time.Now()) {
expected := user.DeepCopy() expected := user.DeepCopy()
// unblock user // unblock user
if user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] == "true" { active := iamv1alpha2.UserActive
expected.Status = iamv1alpha2.UserStatus{ expected.Status = iamv1alpha2.UserStatus{
State: iamv1alpha2.UserActive, State: &active,
LastTransitionTime: &metav1.Time{Time: time.Now()}, LastTransitionTime: &metav1.Time{Time: time.Now()},
}
}
if !reflect.DeepEqual(expected.Status, user.Status) {
return c.ksClient.IamV1alpha2().Users().Update(expected)
} }
return c.ksClient.IamV1alpha2().Users().UpdateStatus(expected)
} }
} }
@@ -670,7 +571,8 @@ func (c *Controller) syncUserStatus(user *iamv1alpha2.User) (*iamv1alpha2.User,
now := time.Now() now := time.Now()
failedLoginAttempts := 0 failedLoginAttempts := 0
for _, loginRecord := range records { for _, loginRecord := range records {
if !loginRecord.Spec.Success && loginRecord.CreationTimestamp.Add(c.authenticationOptions.AuthenticateRateLimiterDuration).After(now) { if !loginRecord.Spec.Success &&
loginRecord.CreationTimestamp.Add(c.authenticationOptions.AuthenticateRateLimiterDuration).After(now) {
failedLoginAttempts++ failedLoginAttempts++
} }
} }
@@ -678,26 +580,29 @@ func (c *Controller) syncUserStatus(user *iamv1alpha2.User) (*iamv1alpha2.User,
// block user if failed login attempts exceeds maximum tries setting // block user if failed login attempts exceeds maximum tries setting
if failedLoginAttempts >= c.authenticationOptions.AuthenticateRateLimiterMaxTries { if failedLoginAttempts >= c.authenticationOptions.AuthenticateRateLimiterMaxTries {
expect := user.DeepCopy() expect := user.DeepCopy()
limitExceed := iamv1alpha2.UserAuthLimitExceeded
expect.Status = iamv1alpha2.UserStatus{ expect.Status = iamv1alpha2.UserStatus{
State: iamv1alpha2.UserAuthLimitExceeded, State: &limitExceed,
Reason: fmt.Sprintf("Failed login attempts exceed %d in last %s", failedLoginAttempts, c.authenticationOptions.AuthenticateRateLimiterDuration), Reason: fmt.Sprintf("Failed login attempts exceed %d in last %s", failedLoginAttempts, c.authenticationOptions.AuthenticateRateLimiterDuration),
LastTransitionTime: &metav1.Time{Time: time.Now()}, LastTransitionTime: &metav1.Time{Time: time.Now()},
} }
// block user for AuthenticateRateLimiterDuration duration, after that put it back to the queue to unblock // block user for AuthenticateRateLimiterDuration duration, after that put it back to the queue to unblock
c.workqueue.AddAfter(user.Name, c.authenticationOptions.AuthenticateRateLimiterDuration) c.Workqueue.AddAfter(user.Name, c.authenticationOptions.AuthenticateRateLimiterDuration)
return c.ksClient.IamV1alpha2().Users().UpdateStatus(expect)
return c.ksClient.IamV1alpha2().Users().Update(expect)
} }
return user, nil return user, nil
} }
func encrypt(password string) (string, error) { func encrypt(password string) (string, error) {
// when user is already mapped to another identity, password is empty by default
// unable to log in directly until password reset
if password == "" {
return "", nil
}
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err return string(bytes), err
} }
// isEncrypted returns whether the given password is encrypted
func isEncrypted(password string) bool {
// bcrypt.Cost returns the hashing cost used to create the given hashed
cost, _ := bcrypt.Cost([]byte(password))
// cost > 0 means the password has been encrypted
return cost > 0
}

View File

@@ -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.ksclient = fake.NewSimpleClientset(f.objects...)
f.k8sclient = k8sfake.NewSimpleClientset(f.kubeobjects...) f.k8sclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
ldapClient := ldapclient.NewSimpleLdap() ldapClient := ldapclient.NewSimpleLdap()
ksinformers := ksinformers.NewSharedInformerFactory(f.ksclient, noResyncPeriodFunc()) ksInformers := ksinformers.NewSharedInformerFactory(f.ksclient, noResyncPeriodFunc())
k8sinformers := kubeinformers.NewSharedInformerFactory(f.k8sclient, noResyncPeriodFunc()) k8sInformers := kubeinformers.NewSharedInformerFactory(f.k8sclient, noResyncPeriodFunc())
for _, user := range f.userLister { 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 { if err != nil {
f.t.Errorf("add user:%s", err) f.t.Errorf("add user:%s", err)
} }
} }
c := NewUserController(f.k8sclient, f.ksclient, nil, c := NewUserController(f.k8sclient, f.ksclient, nil,
ksinformers.Iam().V1alpha2().Users(), ksInformers.Iam().V1alpha2().Users(),
ksInformers.Iam().V1alpha2().LoginRecords(),
nil, nil, nil, nil,
ksinformers.Iam().V1alpha2().LoginRecords(), k8sInformers.Core().V1().ConfigMaps(),
k8sinformers.Core().V1().ConfigMaps(),
ldapClient, nil, ldapClient, nil,
options.NewAuthenticateOptions(), false) options.NewAuthenticateOptions(), false)
c.userSynced = alwaysReady c.Synced = []cache.InformerSynced{alwaysReady}
c.recorder = &record.FakeRecorder{} c.recorder = &record.FakeRecorder{}
return c, ksinformers, k8sinformers return c, ksInformers, k8sInformers
} }
func (f *fixture) run(userName string) { func (f *fixture) run(userName string) {
@@ -230,14 +230,14 @@ func filterInformerActions(actions []core.Action) []core.Action {
func (f *fixture) expectUpdateUserStatusAction(user *iamv1alpha2.User) { func (f *fixture) expectUpdateUserStatusAction(user *iamv1alpha2.User) {
expect := user.DeepCopy() expect := user.DeepCopy()
//expect.Status.State = iamv1alpha2.UserActive
expect.Finalizers = []string{"finalizers.kubesphere.io/users"} expect.Finalizers = []string{"finalizers.kubesphere.io/users"}
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect) action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
f.actions = append(f.actions, action) f.actions = append(f.actions, action)
expect = expect.DeepCopy() 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 = core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
action.Subresource = "status"
f.actions = append(f.actions, action) f.actions = append(f.actions, action)
} }

View File

@@ -39,10 +39,7 @@ func (a *EmailValidator) Handle(ctx context.Context, req admission.Request) admi
} }
allUsers := v1alpha2.UserList{} allUsers := v1alpha2.UserList{}
if err = a.Client.List(ctx, &allUsers, &client.ListOptions{}); err != nil {
err = a.Client.List(ctx, &allUsers, &client.ListOptions{})
if err != nil {
return admission.Errored(http.StatusInternalServerError, err) 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) alreadyExist := emailAlreadyExist(allUsers, user)
if alreadyExist { if alreadyExist {
return admission.Errored(http.StatusConflict, fmt.Errorf("user email: %s already exists", user.Spec.Email)) return admission.Errored(http.StatusConflict, fmt.Errorf("user email: %s already exists", user.Spec.Email))
} }

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,7 @@ limitations under the License.
package v1alpha2 package v1alpha2
import ( import (
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"net/http" "net/http"
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
@@ -25,9 +26,7 @@ import (
v1 "k8s.io/api/rbac/v1" v1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/iam"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" 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/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/models/iam/am"
@@ -42,9 +41,9 @@ const (
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"} 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) ws := runtime.NewWebService(GroupVersion)
handler := newIAMHandler(im, am, group, options) handler := newIAMHandler(im, am, group, authorizer)
// users // users
ws.Route(ws.POST("/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"). ws.Route(ws.PUT("/users/{user}/password").
To(handler.ModifyPassword). To(handler.ModifyPassword).
Doc("Reset password of the specified user."). Doc("Reset password of the specified user.").
Reads(iam.PasswordReset{}). Reads(PasswordReset{}).
Param(ws.PathParameter("user", "username")). Param(ws.PathParameter("user", "username")).
Returns(http.StatusOK, api.StatusOK, errors.None). Returns(http.StatusOK, api.StatusOK, errors.None).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.UserTag})) Metadata(restfulspec.KeyOpenAPITags, []string{constants.UserTag}))

View File

@@ -20,62 +20,101 @@ import (
"fmt" "fmt"
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
apierrors "k8s.io/apimachinery/pkg/api/errors" 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/apiserver/pkg/authentication/user"
"k8s.io/klog" "k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/auth"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" 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" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/apiserver/request" "kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/models/auth"
"kubesphere.io/kubesphere/pkg/models/iam/im" "kubesphere.io/kubesphere/pkg/models/iam/im"
"net/http" "net/http"
) )
type handler struct { const (
im im.IdentityManagementInterface KindTokenReview = "TokenReview"
options *authoptions.AuthenticationOptions passwordGrantType = "password"
tokenOperator im.TokenManagementInterface refreshTokenGrantType = "refresh_token"
authenticator im.PasswordAuthenticator )
loginRecorder im.LoginRecorder
type Spec struct {
Token string `json:"token" description:"access token"`
} }
func newHandler(im im.IdentityManagementInterface, tokenOperator im.TokenManagementInterface, authenticator im.PasswordAuthenticator, loginRecorder im.LoginRecorder, options *authoptions.AuthenticationOptions) *handler { type Status struct {
return &handler{im: im, tokenOperator: tokenOperator, authenticator: authenticator, loginRecorder: loginRecorder, options: options} 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 auth.TokenManagementInterface
passwordAuthenticator auth.PasswordAuthenticator
oauth2Authenticator auth.OAuth2Authenticator
loginRecorder auth.LoginRecorder
}
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 // Implement webhook authentication interface
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
func (h *handler) TokenReview(req *restful.Request, resp *restful.Response) { func (h *handler) TokenReview(req *restful.Request, resp *restful.Response) {
var tokenReview auth.TokenReview var tokenReview TokenReview
err := req.ReadEntity(&tokenReview) err := req.ReadEntity(&tokenReview)
if err != nil { if err != nil {
klog.Error(err)
api.HandleBadRequest(resp, req, err) api.HandleBadRequest(resp, req, err)
return return
} }
if err = tokenReview.Validate(); err != nil { if err = tokenReview.Validate(); err != nil {
klog.Error(err)
api.HandleBadRequest(resp, req, err) api.HandleBadRequest(resp, req, err)
return return
} }
authenticated, err := h.tokenOperator.Verify(tokenReview.Spec.Token) authenticated, err := h.tokenOperator.Verify(tokenReview.Spec.Token)
if err != nil { if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, req, err) api.HandleInternalError(resp, req, err)
return return
} }
success := auth.TokenReview{APIVersion: tokenReview.APIVersion, success := TokenReview{APIVersion: tokenReview.APIVersion,
Kind: auth.KindTokenReview, Kind: KindTokenReview,
Status: &auth.Status{ Status: &Status{
Authenticated: true, Authenticated: true,
User: map[string]interface{}{"username": authenticated.GetName(), "uid": authenticated.GetUID()}, 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) conf, err := h.options.OAuthOptions.OAuthClient(clientId)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err) api.HandleError(resp, req, err)
return return
} }
if responseType != "token" { if responseType != "token" {
err := apierrors.NewBadRequest(fmt.Sprintf("Response type %s is not supported", responseType)) err := apierrors.NewBadRequest(fmt.Sprintf("Response type %s is not supported", responseType))
resp.WriteError(http.StatusUnauthorized, err) api.HandleError(resp, req, err)
return return
} }
if !ok { if !ok {
err := apierrors.NewUnauthorized("Unauthorized") err := apierrors.NewUnauthorized("Unauthorized")
resp.WriteError(http.StatusUnauthorized, err) api.HandleError(resp, req, err)
return return
} }
token, err := h.tokenOperator.IssueTo(authenticated) token, err := h.tokenOperator.IssueTo(authenticated)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err) api.HandleError(resp, req, err)
return return
} }
redirectURL, err := conf.ResolveRedirectURL(redirectURI) redirectURL, err := conf.ResolveRedirectURL(redirectURI)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err) api.HandleError(resp, req, err)
return return
} }
@@ -133,105 +171,41 @@ func (h *handler) Authorize(req *restful.Request, resp *restful.Response) {
http.Redirect(resp, req.Request, redirectURL, http.StatusFound) 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") code := req.QueryParameter("code")
name := req.PathParameter("callback") provider := req.PathParameter("callback")
if code == "" { if code == "" {
err := apierrors.NewUnauthorized("Unauthorized: missing code") err := apierrors.NewUnauthorized("Unauthorized: missing code")
resp.WriteError(http.StatusUnauthorized, err) api.HandleError(resp, req, err)
return
} }
providerOptions, err := h.options.OAuthOptions.IdentityProviderOptions(name) authenticated, provider, err := h.oauth2Authenticator.Authenticate(provider, code)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) api.HandleUnauthorized(resp, req, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
resp.WriteError(http.StatusUnauthorized, err) return
} }
oauthIdentityProvider, err := identityprovider.GetOAuthProvider(providerOptions.Type, providerOptions.Provider) result, err := h.tokenOperator.IssueTo(authenticated)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) api.HandleInternalError(resp, req, apierrors.NewInternalError(err))
resp.WriteError(http.StatusUnauthorized, err)
return return
} }
identity, err := oauthIdentityProvider.IdentityExchange(code) requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, nil); err != nil {
if err != nil { klog.Errorf("Failed to record successful login for user %s, error: %v", authenticated.GetName(), err)
err = apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, 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),
})
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, 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
} }
resp.WriteEntity(result) resp.WriteEntity(result)
} }
func (h *handler) Login(request *restful.Request, response *restful.Response) { func (h *handler) Login(request *restful.Request, response *restful.Response) {
var loginRequest auth.LoginRequest var loginRequest LoginRequest
err := request.ReadEntity(&loginRequest) err := request.ReadEntity(&loginRequest)
if err != nil || loginRequest.Username == "" || loginRequest.Password == "" { if err != nil {
response.WriteHeaderAndEntity(http.StatusUnauthorized, fmt.Errorf("empty username or password")) api.HandleBadRequest(response, request, err)
return return
} }
h.passwordGrant(loginRequest.Username, loginRequest.Password, request, response) 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) { func (h *handler) Token(req *restful.Request, response *restful.Response) {
grantType, err := req.BodyParameter("grant_type") grantType, err := req.BodyParameter("grant_type")
if err != nil { if err != nil {
klog.Error(err)
api.HandleBadRequest(response, req, err) api.HandleBadRequest(response, req, err)
return return
} }
switch grantType { switch grantType {
case "password": case passwordGrantType:
username, err := req.BodyParameter("username") username, _ := req.BodyParameter("username")
if err != nil { password, _ := req.BodyParameter("password")
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
}
h.passwordGrant(username, password, req, response) h.passwordGrant(username, password, req, response)
break break
case "refresh_token": case refreshTokenGrantType:
h.refreshTokenGrant(req, response) h.refreshTokenGrant(req, response)
break break
default: default:
err := apierrors.NewBadRequest(fmt.Sprintf("Grant type %s is not supported", grantType)) 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) { 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 { if err != nil {
klog.Error(err)
switch err { switch err {
case im.AuthFailedIncorrectPassword: case auth.IncorrectPasswordError:
if err := h.loginRecorder.RecordLogin(username, iamv1alpha2.Token, "", err, req.Request); err != nil { requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
klog.Error(err) if err := h.loginRecorder.RecordLogin(username, iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, err); err != nil {
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err)) klog.Errorf("Failed to record unsuccessful login attempt for user %s, error: %v", username, err)
return
} }
response.WriteError(http.StatusUnauthorized, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))) api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
return return
case im.AuthFailedIdentityMappingNotMatch: case auth.RateLimitExceededError:
response.WriteError(http.StatusUnauthorized, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))) api.HandleTooManyRequests(response, req, apierrors.NewTooManyRequestsError(fmt.Sprintf("Unauthorized: %s", err)))
return
case im.AuthRateLimitExceeded:
response.WriteError(http.StatusTooManyRequests, apierrors.NewTooManyRequests(fmt.Sprintf("Unauthorized: %s", err), 60))
return return
default: default:
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err)) api.HandleInternalError(response, req, apierrors.NewInternalError(err))
return return
} }
} }
result, err := h.tokenOperator.IssueTo(authenticated) result, err := h.tokenOperator.IssueTo(authenticated)
if err != nil { if err != nil {
klog.Error(err) api.HandleInternalError(response, req, apierrors.NewInternalError(err))
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err))
return return
} }
if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, "", nil, req.Request); err != nil { requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
klog.Error(err) if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, nil); err != nil {
response.WriteError(http.StatusInternalServerError, apierrors.NewInternalError(err)) klog.Errorf("Failed to record successful login for user %s, error: %v", username, err)
return
} }
response.WriteEntity(result) 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) { func (h *handler) refreshTokenGrant(req *restful.Request, response *restful.Response) {
refreshToken, err := req.BodyParameter("refresh_token") refreshToken, err := req.BodyParameter("refresh_token")
if err != nil { if err != nil {
klog.Error(err) api.HandleBadRequest(response, req, apierrors.NewBadRequest(err.Error()))
api.HandleBadRequest(response, req, err)
return return
} }
authenticated, err := h.tokenOperator.Verify(refreshToken) authenticated, err := h.tokenOperator.Verify(refreshToken)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
response.WriteError(http.StatusUnauthorized, err) api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
return 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) result, err := h.tokenOperator.IssueTo(authenticated)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
response.WriteError(http.StatusUnauthorized, err) api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
return return
} }

View File

@@ -20,10 +20,10 @@ import (
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
restfulspec "github.com/emicklei/go-restful-openapi" restfulspec "github.com/emicklei/go-restful-openapi"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/auth"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/auth"
"kubesphere.io/kubesphere/pkg/models/iam/im" "kubesphere.io/kubesphere/pkg/models/iam/im"
"net/http" "net/http"
) )
@@ -34,22 +34,28 @@ import (
// Most authentication integrations place an authenticating proxy in front of this endpoint, or configure ks-apiserver // Most authentication integrations place an authenticating proxy in front of this endpoint, or configure ks-apiserver
// to validate credentials against a backing identity provider. // 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. // 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 := &restful.WebService{}
ws.Path("/oauth"). ws.Path("/oauth").
Consumes(restful.MIME_JSON). Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON) Produces(restful.MIME_JSON)
handler := newHandler(im, tokenOperator, authenticator, loginRecorder, options) handler := newHandler(im, tokenOperator, passwordAuthenticator, oauth2Authenticator, loginRecorder, options)
// Implement webhook authentication interface // Implement webhook authentication interface
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
ws.Route(ws.POST("/authenticate"). ws.Route(ws.POST("/authenticate").
Doc("TokenReview attempts to authenticate a token to a known user. Note: TokenReview requests may be "+ 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."). "cached by the webhook token authenticator plugin in the kube-apiserver.").
Reads(auth.TokenReview{}). Reads(TokenReview{}).
To(handler.TokenReview). To(handler.TokenReview).
Returns(http.StatusOK, api.StatusOK, auth.TokenReview{}). Returns(http.StatusOK, api.StatusOK, TokenReview{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag})) Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))
// Only support implicit grant flow // 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)). "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."+ Param(ws.QueryParameter("state", "if the \"state\" parameter was present in the client authorization request."+
"The exact value received from the client.").Required(true)). "The exact value received from the client.").Required(true)).
To(handler.oAuthCallBack). To(handler.oauthCallBack).
Returns(http.StatusOK, api.StatusOK, oauth.Token{}). Returns(http.StatusOK, api.StatusOK, oauth.Token{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag})) Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))
@@ -113,7 +119,7 @@ func AddToContainer(c *restful.Container, im im.IdentityManagementInterface, tok
To(handler.Login). To(handler.Login).
Deprecate(). 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."). 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{}). Returns(http.StatusOK, api.StatusOK, oauth.Token{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag})) Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))

View File

@@ -21,26 +21,25 @@ import (
"k8s.io/klog" "k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query" "kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/components" "kubesphere.io/kubesphere/pkg/models/components"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
resourcev1alpha2 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource" 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"
"kubesphere.io/kubesphere/pkg/server/params" "kubesphere.io/kubesphere/pkg/server/params"
"strings" "strings"
) )
type Handler struct { type Handler struct {
resourceGetterV1alpha3 *resource.ResourceGetter resourceGetterV1alpha3 *resourcev1alpha3.ResourceGetter
resourcesGetterV1alpha2 *resourcev1alpha2.ResourceGetter resourcesGetterV1alpha2 *resourcev1alpha2.ResourceGetter
componentsGetter components.ComponentsGetter componentsGetter components.ComponentsGetter
} }
func New(factory informers.InformerFactory) *Handler { func New(resourceGetterV1alpha3 *resourcev1alpha3.ResourceGetter, resourcesGetterV1alpha2 *resourcev1alpha2.ResourceGetter, componentsGetter components.ComponentsGetter) *Handler {
return &Handler{ return &Handler{
resourceGetterV1alpha3: resource.NewResourceGetter(factory), resourceGetterV1alpha3: resourceGetterV1alpha3,
resourcesGetterV1alpha2: resourcev1alpha2.NewResourceGetter(factory), resourcesGetterV1alpha2: resourcesGetterV1alpha2,
componentsGetter: components.NewComponentsGetter(factory.KubernetesSharedInformerFactory()), componentsGetter: componentsGetter,
} }
} }
@@ -55,7 +54,7 @@ func (h *Handler) handleGetResources(request *restful.Request, response *restful
return return
} }
if err != resource.ErrResourceNotSupported { if err != resourcev1alpha3.ErrResourceNotSupported {
klog.Error(err, resourceType) klog.Error(err, resourceType)
api.HandleInternalError(response, nil, err) api.HandleInternalError(response, nil, err)
return return
@@ -87,7 +86,7 @@ func (h *Handler) handleListResources(request *restful.Request, response *restfu
return return
} }
if err != resource.ErrResourceNotSupported { if err != resourcev1alpha3.ErrResourceNotSupported {
klog.Error(err, resourceType) klog.Error(err, resourceType)
api.HandleInternalError(response, nil, err) api.HandleInternalError(response, nil, err)
return return

View File

@@ -29,7 +29,10 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/query" "kubesphere.io/kubesphere/pkg/apiserver/query"
fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake" fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
"kubesphere.io/kubesphere/pkg/informers" "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" "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" fakeapp "sigs.k8s.io/application/pkg/client/clientset/versioned/fake"
"testing" "testing"
) )
@@ -88,7 +91,9 @@ func TestResourceV1alpha2Fallback(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
handler := New(factory) handler := New(resourcev1alpha3.NewResourceGetter(factory),
resourcev1alpha2.NewResourceGetter(factory),
components.NewComponentsGetter(factory.KubernetesSharedInformerFactory()))
for _, test := range tests { for _, test := range tests {
got, err := listResources(test.namespace, test.resource, test.query, handler) got, err := listResources(test.namespace, test.resource, test.query, handler)

View File

@@ -25,6 +25,9 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/query" "kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/informers" "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" "net/http"
) )
@@ -47,7 +50,9 @@ func Resource(resource string) schema.GroupResource {
func AddToContainer(c *restful.Container, informerFactory informers.InformerFactory) error { func AddToContainer(c *restful.Container, informerFactory informers.InformerFactory) error {
webservice := runtime.NewWebService(GroupVersion) webservice := runtime.NewWebService(GroupVersion)
handler := New(informerFactory) handler := New(resourcev1alpha3.NewResourceGetter(informerFactory),
resourcev1alpha2.NewResourceGetter(informerFactory),
components.NewComponentsGetter(informerFactory.KubernetesSharedInformerFactory()))
webservice.Route(webservice.GET("/{resources}"). webservice.Route(webservice.GET("/{resources}").
To(handler.handleListResources). To(handler.handleListResources).

View File

@@ -0,0 +1,259 @@
/*
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 auth
import (
"fmt"
"golang.org/x/crypto/bcrypt"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/constants"
"net/mail"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
authuser "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
)
var (
RateLimitExceededError = fmt.Errorf("auth rate limit exceeded")
IncorrectPasswordError = fmt.Errorf("incorrect password")
AccountIsNotActiveError = fmt.Errorf("account is not active")
)
type PasswordAuthenticator interface {
Authenticate(username, password string) (authuser.Info, string, error)
}
type OAuth2Authenticator interface {
Authenticate(provider, code string) (authuser.Info, string, error)
}
type passwordAuthenticator struct {
ksClient kubesphere.Interface
userGetter *userGetter
authOptions *authoptions.AuthenticationOptions
}
type oauth2Authenticator struct {
ksClient kubesphere.Interface
userGetter *userGetter
authOptions *authoptions.AuthenticationOptions
}
type userGetter struct {
userLister iamv1alpha2listers.UserLister
}
func NewPasswordAuthenticator(ksClient kubesphere.Interface,
userLister iamv1alpha2listers.UserLister,
options *authoptions.AuthenticationOptions) PasswordAuthenticator {
passwordAuthenticator := &passwordAuthenticator{
ksClient: ksClient,
userGetter: &userGetter{userLister: userLister},
authOptions: options,
}
return passwordAuthenticator
}
func NewOAuth2Authenticator(ksClient kubesphere.Interface,
userLister iamv1alpha2listers.UserLister,
options *authoptions.AuthenticationOptions) OAuth2Authenticator {
oauth2Authenticator := &oauth2Authenticator{
ksClient: ksClient,
userGetter: &userGetter{userLister: userLister},
authOptions: options,
}
return oauth2Authenticator
}
func (p *passwordAuthenticator) Authenticate(username, password string) (authuser.Info, string, error) {
// empty username or password are not allowed
if username == "" || password == "" {
return nil, "", IncorrectPasswordError
}
// generic identity provider has higher priority
for _, providerOptions := range p.authOptions.OAuthOptions.IdentityProviders {
// the admin account in kubesphere has the highest priority
if username == constants.AdminUserName {
break
}
if genericProvider, _ := identityprovider.CreateGenericProvider(providerOptions.Type, providerOptions.Provider); genericProvider != nil {
authenticated, err := genericProvider.Authenticate(username, password)
if err != nil {
if errors.IsUnauthorized(err) {
continue
}
return nil, providerOptions.Name, err
}
linkedAccount, err := p.userGetter.findLinkedAccount(providerOptions.Name, authenticated.GetUserID())
// using this method requires you to manually provision users.
if providerOptions.MappingMethod == oauth.MappingMethodLookup && linkedAccount == nil {
continue
}
if linkedAccount != nil {
return &authuser.DefaultInfo{Name: linkedAccount.GetName()}, providerOptions.Name, nil
}
// the user will automatically create and mapping when login successful.
if providerOptions.MappingMethod == oauth.MappingMethodAuto {
return preRegistrationUser(providerOptions.Name, authenticated), providerOptions.Name, nil
}
}
}
// kubesphere account
user, err := p.userGetter.findUser(username)
if err != nil {
// ignore not found error
if !errors.IsNotFound(err) {
klog.Error(err)
return nil, "", err
}
}
// check user status
if user != nil && (user.Status.State == nil || *user.Status.State != iamv1alpha2.UserActive) {
if user.Status.State != nil && *user.Status.State == iamv1alpha2.UserAuthLimitExceeded {
klog.Errorf("%s, username: %s", RateLimitExceededError, username)
return nil, "", RateLimitExceededError
} else {
// state not active
klog.Errorf("%s, username: %s", AccountIsNotActiveError, username)
return nil, "", AccountIsNotActiveError
}
}
// if the password is not empty, means that the password has been reset, even if the user was mapping from IDP
if user != nil && user.Spec.EncryptedPassword != "" {
if err = PasswordVerify(user.Spec.EncryptedPassword, password); err != nil {
klog.Error(err)
return nil, "", err
}
u := &authuser.DefaultInfo{
Name: user.Name,
}
// check if the password is initialized
if uninitialized := user.Annotations[iamv1alpha2.UninitializedAnnotation]; uninitialized != "" {
u.Extra = map[string][]string{
iamv1alpha2.ExtraUninitialized: {uninitialized},
}
}
return u, "", nil
}
return nil, "", IncorrectPasswordError
}
func preRegistrationUser(idp string, identity identityprovider.Identity) authuser.Info {
return &authuser.DefaultInfo{
Name: iamv1alpha2.PreRegistrationUser,
Extra: map[string][]string{
iamv1alpha2.ExtraIdentityProvider: {idp},
iamv1alpha2.ExtraUID: {identity.GetUserID()},
iamv1alpha2.ExtraUsername: {identity.GetUsername()},
iamv1alpha2.ExtraEmail: {identity.GetEmail()},
iamv1alpha2.ExtraDisplayName: {identity.GetDisplayName()},
},
Groups: []string{iamv1alpha2.PreRegistrationUserGroup},
}
}
func (o oauth2Authenticator) Authenticate(provider, code string) (authuser.Info, string, error) {
providerOptions, err := o.authOptions.OAuthOptions.IdentityProviderOptions(provider)
// identity provider not registered
if err != nil {
klog.Error(err)
return nil, "", err
}
oauthIdentityProvider, err := identityprovider.CreateOAuthProvider(providerOptions.Type, providerOptions.Provider)
if err != nil {
klog.Error(err)
return nil, "", err
}
authenticated, err := oauthIdentityProvider.IdentityExchange(code)
if err != nil {
klog.Error(err)
return nil, "", err
}
user, err := o.userGetter.findLinkedAccount(providerOptions.Name, authenticated.GetUserID())
if user == nil && providerOptions.MappingMethod == oauth.MappingMethodLookup {
klog.Error(err)
return nil, "", err
}
// the user will automatically create and mapping when login successful.
if user == nil && providerOptions.MappingMethod == oauth.MappingMethodAuto {
return preRegistrationUser(providerOptions.Name, authenticated), providerOptions.Name, nil
}
if user != nil {
return &authuser.DefaultInfo{Name: user.GetName()}, providerOptions.Name, nil
}
return nil, "", errors.NewNotFound(iamv1alpha2.Resource("user"), authenticated.GetUsername())
}
func PasswordVerify(encryptedPassword, password string) error {
if err := bcrypt.CompareHashAndPassword([]byte(encryptedPassword), []byte(password)); err != nil {
return IncorrectPasswordError
}
return nil
}
// findUser
func (u *userGetter) findUser(username string) (*iamv1alpha2.User, error) {
if _, err := mail.ParseAddress(username); err != nil {
return u.userLister.Get(username)
} else {
users, err := u.userLister.List(labels.Everything())
if err != nil {
klog.Error(err)
return nil, err
}
for _, find := range users {
if find.Spec.Email == username {
return find, nil
}
}
}
return nil, errors.NewNotFound(iamv1alpha2.Resource("user"), username)
}
func (u *userGetter) findLinkedAccount(idp, uid string) (*iamv1alpha2.User, error) {
selector := labels.SelectorFromSet(labels.Set{
iamv1alpha2.IdentifyProviderLabel: idp,
iamv1alpha2.OriginUIDLabel: uid,
})
users, err := u.userLister.List(selector)
if err != nil {
klog.Error(err)
return nil, err
}
if len(users) != 1 {
return nil, errors.NewNotFound(iamv1alpha2.Resource("user"), uid)
}
return users[0], err
}

View File

@@ -0,0 +1,40 @@
/*
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 auth
import (
"golang.org/x/crypto/bcrypt"
"testing"
)
func TestEncryptPassword(t *testing.T) {
password := "P@88w0rd"
encryptedPassword, err := hashPassword(password)
if err != nil {
t.Fatal(err)
}
if err = PasswordVerify(encryptedPassword, password); err != nil {
t.Fatal(err)
}
}
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
return string(bytes), err
}

View File

@@ -1,22 +1,20 @@
/* /*
* Copyright 2020 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
*
* 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 im 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 auth
import ( import (
"fmt" "fmt"
@@ -24,13 +22,11 @@ import (
"k8s.io/klog" "k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned" kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/utils/net"
"net/http"
"strings" "strings"
) )
type LoginRecorder interface { type LoginRecorder interface {
RecordLogin(username string, loginType iamv1alpha2.LoginType, provider string, authErr error, req *http.Request) error RecordLogin(username string, loginType iamv1alpha2.LoginType, provider string, sourceIP string, userAgent string, authErr error) error
} }
type loginRecorder struct { type loginRecorder struct {
@@ -43,7 +39,7 @@ func NewLoginRecorder(ksClient kubesphere.Interface) LoginRecorder {
} }
} }
func (l *loginRecorder) RecordLogin(username string, loginType iamv1alpha2.LoginType, provider string, authErr error, req *http.Request) error { func (l *loginRecorder) RecordLogin(username string, loginType iamv1alpha2.LoginType, provider string, sourceIP string, userAgent string, authErr error) error {
// This is a temporary solution in case of user login with email, // This is a temporary solution in case of user login with email,
// '@' is not allowed in Kubernetes object name. // '@' is not allowed in Kubernetes object name.
username = strings.Replace(username, "@", "-", -1) username = strings.Replace(username, "@", "-", -1)
@@ -60,8 +56,8 @@ func (l *loginRecorder) RecordLogin(username string, loginType iamv1alpha2.Login
Provider: provider, Provider: provider,
Success: true, Success: true,
Reason: iamv1alpha2.AuthenticatedSuccessfully, Reason: iamv1alpha2.AuthenticatedSuccessfully,
SourceIP: net.GetRequestIP(req), SourceIP: sourceIP,
UserAgent: req.UserAgent(), UserAgent: userAgent,
}, },
} }

View File

@@ -16,7 +16,7 @@
*/ */
package im package auth
import ( import (
"fmt" "fmt"

View File

@@ -18,22 +18,30 @@ package am
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
listersv1 "k8s.io/client-go/listers/core/v1"
"k8s.io/klog" "k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
devopsv1alpha3 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/apiserver/query" "kubesphere.io/kubesphere/pkg/apiserver/query"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned" kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
devopslisters "kubesphere.io/kubesphere/pkg/client/listers/devops/v1alpha3"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/informers"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource" resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/clusterrole"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/clusterrolebinding"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/globalrole"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/globalrolebinding"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/role"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/rolebinding"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/workspacerole"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/workspacerolebinding"
"kubesphere.io/kubesphere/pkg/utils/sliceutil" "kubesphere.io/kubesphere/pkg/utils/sliceutil"
) )
@@ -87,45 +95,55 @@ type AccessManagementInterface interface {
} }
type amOperator struct { type amOperator struct {
resourceGetter *resourcev1alpha3.ResourceGetter globalRoleBindingGetter resourcev1alpha3.Interface
ksclient kubesphere.Interface workspaceRoleBindingGetter resourcev1alpha3.Interface
k8sclient kubernetes.Interface clusterRoleBindingGetter resourcev1alpha3.Interface
roleBindingGetter resourcev1alpha3.Interface
globalRoleGetter resourcev1alpha3.Interface
workspaceRoleGetter resourcev1alpha3.Interface
clusterRoleGetter resourcev1alpha3.Interface
roleGetter resourcev1alpha3.Interface
devopsProjectLister devopslisters.DevOpsProjectLister
namespaceLister listersv1.NamespaceLister
ksclient kubesphere.Interface
k8sclient kubernetes.Interface
} }
func NewReadOnlyOperator(factory informers.InformerFactory) AccessManagementInterface { func NewReadOnlyOperator(factory informers.InformerFactory) AccessManagementInterface {
return &amOperator{ return &amOperator{
resourceGetter: resourcev1alpha3.NewResourceGetter(factory), globalRoleBindingGetter: globalrolebinding.New(factory.KubeSphereSharedInformerFactory()),
workspaceRoleBindingGetter: workspacerolebinding.New(factory.KubeSphereSharedInformerFactory()),
clusterRoleBindingGetter: clusterrolebinding.New(factory.KubernetesSharedInformerFactory()),
roleBindingGetter: rolebinding.New(factory.KubernetesSharedInformerFactory()),
globalRoleGetter: globalrole.New(factory.KubeSphereSharedInformerFactory()),
workspaceRoleGetter: workspacerole.New(factory.KubeSphereSharedInformerFactory()),
clusterRoleGetter: clusterrole.New(factory.KubernetesSharedInformerFactory()),
roleGetter: role.New(factory.KubernetesSharedInformerFactory()),
devopsProjectLister: factory.KubeSphereSharedInformerFactory().Devops().V1alpha3().DevOpsProjects().Lister(),
namespaceLister: factory.KubernetesSharedInformerFactory().Core().V1().Namespaces().Lister(),
} }
} }
func NewOperator(factory informers.InformerFactory, ksclient kubesphere.Interface, k8sclient kubernetes.Interface) AccessManagementInterface { func NewOperator(ksClient kubesphere.Interface, k8sClient kubernetes.Interface, factory informers.InformerFactory) AccessManagementInterface {
return &amOperator{ amOperator := NewReadOnlyOperator(factory).(*amOperator)
resourceGetter: resourcev1alpha3.NewResourceGetter(factory), amOperator.ksclient = ksClient
ksclient: ksclient, amOperator.k8sclient = k8sClient
k8sclient: k8sclient, return amOperator
}
} }
func (am *amOperator) GetGlobalRoleOfUser(username string) (*iamv1alpha2.GlobalRole, error) { func (am *amOperator) GetGlobalRoleOfUser(username string) (*iamv1alpha2.GlobalRole, error) {
globalRoleBindings, err := am.ListGlobalRoleBindings(username)
userRoleBindings, err := am.ListGlobalRoleBindings(username) if len(globalRoleBindings) > 0 {
// Usually, only one globalRoleBinding will be found which is created from ks-console.
if len(userRoleBindings) > 0 { if len(globalRoleBindings) > 1 {
role, err := am.GetGlobalRole(userRoleBindings[0].RoleRef.Name) klog.Warningf("conflict global role binding, username: %s", username)
}
globalRole, err := am.GetGlobalRole(globalRoleBindings[0].RoleRef.Name)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
if len(userRoleBindings) > 1 { return globalRole, nil
klog.Warningf("conflict global role binding, username: %s", username)
}
out := role.DeepCopy()
if out.Annotations == nil {
out.Annotations = make(map[string]string, 0)
}
out.Annotations[iamv1alpha2.GlobalRoleAnnotation] = role.Name
return out, nil
} }
err = errors.NewNotFound(iamv1alpha2.Resource(iamv1alpha2.ResourcesSingularGlobalRoleBinding), username) err = errors.NewNotFound(iamv1alpha2.Resource(iamv1alpha2.ResourcesSingularGlobalRoleBinding), username)
@@ -244,7 +262,7 @@ func (am *amOperator) GetClusterRoleOfUser(username string) (*rbacv1.ClusterRole
} }
func (am *amOperator) ListWorkspaceRoleBindings(username string, groups []string, workspace string) ([]*iamv1alpha2.WorkspaceRoleBinding, error) { func (am *amOperator) ListWorkspaceRoleBindings(username string, groups []string, workspace string) ([]*iamv1alpha2.WorkspaceRoleBinding, error) {
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralWorkspaceRoleBinding, "", query.New()) roleBindings, err := am.workspaceRoleBindingGetter.List("", query.New())
if err != nil { if err != nil {
return nil, err return nil, err
@@ -265,8 +283,7 @@ func (am *amOperator) ListWorkspaceRoleBindings(username string, groups []string
func (am *amOperator) ListClusterRoleBindings(username string) ([]*rbacv1.ClusterRoleBinding, error) { func (am *amOperator) ListClusterRoleBindings(username string) ([]*rbacv1.ClusterRoleBinding, error) {
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralClusterRoleBinding, "", query.New()) roleBindings, err := am.clusterRoleBindingGetter.List("", query.New())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -283,14 +300,12 @@ func (am *amOperator) ListClusterRoleBindings(username string) ([]*rbacv1.Cluste
} }
func (am *amOperator) ListGlobalRoleBindings(username string) ([]*iamv1alpha2.GlobalRoleBinding, error) { func (am *amOperator) ListGlobalRoleBindings(username string) ([]*iamv1alpha2.GlobalRoleBinding, error) {
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralGlobalRoleBinding, "", query.New()) roleBindings, err := am.globalRoleBindingGetter.List("", query.New())
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := make([]*iamv1alpha2.GlobalRoleBinding, 0) result := make([]*iamv1alpha2.GlobalRoleBinding, 0)
for _, obj := range roleBindings.Items { for _, obj := range roleBindings.Items {
roleBinding := obj.(*iamv1alpha2.GlobalRoleBinding) roleBinding := obj.(*iamv1alpha2.GlobalRoleBinding)
if contains(roleBinding.Subjects, username, nil) { if contains(roleBinding.Subjects, username, nil) {
@@ -302,7 +317,7 @@ func (am *amOperator) ListGlobalRoleBindings(username string) ([]*iamv1alpha2.Gl
} }
func (am *amOperator) ListRoleBindings(username string, groups []string, namespace string) ([]*rbacv1.RoleBinding, error) { func (am *amOperator) ListRoleBindings(username string, groups []string, namespace string) ([]*rbacv1.RoleBinding, error) {
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralRoleBinding, namespace, query.New()) roleBindings, err := am.roleBindingGetter.List(namespace, query.New())
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
@@ -335,23 +350,23 @@ func contains(subjects []rbacv1.Subject, username string, groups []string) bool
} }
func (am *amOperator) ListRoles(namespace string, query *query.Query) (*api.ListResult, error) { func (am *amOperator) ListRoles(namespace string, query *query.Query) (*api.ListResult, error) {
return am.resourceGetter.List(iamv1alpha2.ResourcesPluralRole, namespace, query) return am.roleGetter.List(namespace, query)
} }
func (am *amOperator) ListClusterRoles(query *query.Query) (*api.ListResult, error) { func (am *amOperator) ListClusterRoles(query *query.Query) (*api.ListResult, error) {
return am.resourceGetter.List(iamv1alpha2.ResourcesPluralClusterRole, "", query) return am.clusterRoleGetter.List("", query)
} }
func (am *amOperator) ListWorkspaceRoles(queryParam *query.Query) (*api.ListResult, error) { func (am *amOperator) ListWorkspaceRoles(queryParam *query.Query) (*api.ListResult, error) {
return am.resourceGetter.List(iamv1alpha2.ResourcesPluralWorkspaceRole, "", queryParam) return am.workspaceRoleGetter.List("", queryParam)
} }
func (am *amOperator) ListGlobalRoles(query *query.Query) (*api.ListResult, error) { func (am *amOperator) ListGlobalRoles(query *query.Query) (*api.ListResult, error) {
return am.resourceGetter.List(iamv1alpha2.ResourcesPluralGlobalRole, "", query) return am.globalRoleGetter.List("", query)
} }
func (am *amOperator) GetGlobalRole(globalRole string) (*iamv1alpha2.GlobalRole, error) { func (am *amOperator) GetGlobalRole(globalRole string) (*iamv1alpha2.GlobalRole, error) {
obj, err := am.resourceGetter.Get(iamv1alpha2.ResourcesPluralGlobalRole, "", globalRole) obj, err := am.globalRoleGetter.Get("", globalRole)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
@@ -938,7 +953,7 @@ func (am *amOperator) GetRoleReferenceRules(roleRef rbacv1.RoleRef, namespace st
} }
func (am *amOperator) GetWorkspaceRole(workspace string, name string) (*iamv1alpha2.WorkspaceRole, error) { func (am *amOperator) GetWorkspaceRole(workspace string, name string) (*iamv1alpha2.WorkspaceRole, error) {
obj, err := am.resourceGetter.Get(iamv1alpha2.ResourcesPluralWorkspaceRole, "", name) obj, err := am.workspaceRoleGetter.Get("", name)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
@@ -956,7 +971,7 @@ func (am *amOperator) GetWorkspaceRole(workspace string, name string) (*iamv1alp
} }
func (am *amOperator) GetNamespaceRole(namespace string, name string) (*rbacv1.Role, error) { func (am *amOperator) GetNamespaceRole(namespace string, name string) (*rbacv1.Role, error) {
obj, err := am.resourceGetter.Get(iamv1alpha2.ResourcesPluralRole, namespace, name) obj, err := am.roleGetter.Get(namespace, name)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
@@ -965,7 +980,7 @@ func (am *amOperator) GetNamespaceRole(namespace string, name string) (*rbacv1.R
} }
func (am *amOperator) GetClusterRole(name string) (*rbacv1.ClusterRole, error) { func (am *amOperator) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
obj, err := am.resourceGetter.Get(iamv1alpha2.ResourcesPluralClusterRole, "", name) obj, err := am.clusterRoleGetter.Get("", name)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
@@ -973,28 +988,25 @@ func (am *amOperator) GetClusterRole(name string) (*rbacv1.ClusterRole, error) {
return obj.(*rbacv1.ClusterRole), nil return obj.(*rbacv1.ClusterRole), nil
} }
func (am *amOperator) GetDevOpsRelatedNamespace(devops string) (string, error) { func (am *amOperator) GetDevOpsRelatedNamespace(devops string) (string, error) {
obj, err := am.resourceGetter.Get(devopsv1alpha3.ResourcePluralDevOpsProject, "", devops) devopsProject, err := am.devopsProjectLister.Get(devops)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return "", err return "", err
} }
devopsProject := obj.(*devopsv1alpha3.DevOpsProject)
return devopsProject.Status.AdminNamespace, nil return devopsProject.Status.AdminNamespace, nil
} }
func (am *amOperator) GetDevOpsControlledWorkspace(devops string) (string, error) { func (am *amOperator) GetDevOpsControlledWorkspace(devops string) (string, error) {
obj, err := am.resourceGetter.Get(devopsv1alpha3.ResourcePluralDevOpsProject, "", devops) devopsProject, err := am.devopsProjectLister.Get(devops)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return "", err return "", err
} }
devopsProject := obj.(*devopsv1alpha3.DevOpsProject)
return devopsProject.Labels[tenantv1alpha1.WorkspaceLabel], nil return devopsProject.Labels[tenantv1alpha1.WorkspaceLabel], nil
} }
func (am *amOperator) GetNamespaceControlledWorkspace(namespace string) (string, error) { func (am *amOperator) GetNamespaceControlledWorkspace(namespace string) (string, error) {
obj, err := am.resourceGetter.Get("namespaces", "", namespace) ns, err := am.namespaceLister.Get(namespace)
if err != nil { if err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
return "", nil return "", nil
@@ -1002,24 +1014,22 @@ func (am *amOperator) GetNamespaceControlledWorkspace(namespace string) (string,
klog.Error(err) klog.Error(err)
return "", err return "", err
} }
ns := obj.(*corev1.Namespace)
return ns.Labels[tenantv1alpha1.WorkspaceLabel], nil return ns.Labels[tenantv1alpha1.WorkspaceLabel], nil
} }
func (am *amOperator) ListGroupWorkspaceRoleBindings(workspace, group string) ([]*iamv1alpha2.WorkspaceRoleBinding, error) { func (am *amOperator) ListGroupWorkspaceRoleBindings(workspace, group string) ([]*iamv1alpha2.WorkspaceRoleBinding, error) {
q := workspaceQuery(workspace) queryParam := query.New()
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralWorkspaceRoleBinding, "", q) queryParam.LabelSelector = labels.FormatLabels(map[string]string{tenantv1alpha1.WorkspaceLabel: workspace})
roleBindings, err := am.workspaceRoleBindingGetter.List("", queryParam)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := make([]*iamv1alpha2.WorkspaceRoleBinding, 0) result := make([]*iamv1alpha2.WorkspaceRoleBinding, 0)
for _, obj := range roleBindings.Items { for _, obj := range roleBindings.Items {
roleBinding := obj.(*iamv1alpha2.WorkspaceRoleBinding) roleBinding := obj.(*iamv1alpha2.WorkspaceRoleBinding)
inSpecifiedWorkspace := workspace == "" || roleBinding.Labels[tenantv1alpha1.WorkspaceLabel] == workspace inSpecifiedWorkspace := workspace == "" || roleBinding.Labels[tenantv1alpha1.WorkspaceLabel] == workspace
if containsgroup(roleBinding.Subjects, group) && inSpecifiedWorkspace { if containsGroup(roleBinding.Subjects, group) && inSpecifiedWorkspace {
result = append(result, roleBinding) result = append(result, roleBinding)
} }
} }
@@ -1062,15 +1072,13 @@ func (am *amOperator) DeleteWorkspaceRoleBinding(workspaceName, name string) err
} }
func (am *amOperator) ListGroupRoleBindings(workspace, group string) ([]*rbacv1.RoleBinding, error) { func (am *amOperator) ListGroupRoleBindings(workspace, group string) ([]*rbacv1.RoleBinding, error) {
q := workspaceQuery(workspace) namespaces, err := am.namespaceLister.List(labels.SelectorFromSet(labels.Set{tenantv1alpha1.WorkspaceLabel: workspace}))
namespaces, err := am.resourceGetter.List("namespaces", "", q)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := make([]*rbacv1.RoleBinding, 0) result := make([]*rbacv1.RoleBinding, 0)
for _, ns := range namespaces.Items { for _, namespace := range namespaces {
namespace := ns.(*corev1.Namespace) roleBindings, err := am.roleBindingGetter.List(namespace.Name, query.New())
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralRoleBinding, namespace.Name, query.New())
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
@@ -1078,7 +1086,7 @@ func (am *amOperator) ListGroupRoleBindings(workspace, group string) ([]*rbacv1.
for _, obj := range roleBindings.Items { for _, obj := range roleBindings.Items {
roleBinding := obj.(*rbacv1.RoleBinding) roleBinding := obj.(*rbacv1.RoleBinding)
if containsgroup(roleBinding.Subjects, group) { if containsGroup(roleBinding.Subjects, group) {
result = append(result, roleBinding) result = append(result, roleBinding)
} }
} }
@@ -1087,23 +1095,20 @@ func (am *amOperator) ListGroupRoleBindings(workspace, group string) ([]*rbacv1.
} }
func (am *amOperator) ListGroupDevOpsRoleBindings(workspace, group string) ([]*rbacv1.RoleBinding, error) { func (am *amOperator) ListGroupDevOpsRoleBindings(workspace, group string) ([]*rbacv1.RoleBinding, error) {
q := workspaceQuery(workspace) devOpsProjects, err := am.devopsProjectLister.List(labels.SelectorFromSet(labels.Set{tenantv1alpha1.WorkspaceLabel: workspace}))
namespaces, err := am.resourceGetter.List(devopsv1alpha3.ResourcePluralDevOpsProject, "", q)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := make([]*rbacv1.RoleBinding, 0) result := make([]*rbacv1.RoleBinding, 0)
for _, ns := range namespaces.Items { for _, devOpsProject := range devOpsProjects {
namespace := ns.(*devopsv1alpha3.DevOpsProject) roleBindings, err := am.roleBindingGetter.List(devOpsProject.Name, query.New())
roleBindings, err := am.resourceGetter.List(iamv1alpha2.ResourcesPluralRoleBinding, namespace.Name, query.New())
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
for _, obj := range roleBindings.Items { for _, obj := range roleBindings.Items {
roleBinding := obj.(*rbacv1.RoleBinding) roleBinding := obj.(*rbacv1.RoleBinding)
if containsgroup(roleBinding.Subjects, group) { if containsGroup(roleBinding.Subjects, group) {
result = append(result, roleBinding) result = append(result, roleBinding)
} }
} }
@@ -1143,7 +1148,7 @@ func (am *amOperator) DeleteRoleBinding(namespace, name string) error {
return am.k8sclient.RbacV1().RoleBindings(namespace).Delete(name, metav1.NewDeleteOptions(0)) return am.k8sclient.RbacV1().RoleBindings(namespace).Delete(name, metav1.NewDeleteOptions(0))
} }
func containsgroup(subjects []rbacv1.Subject, group string) bool { func containsGroup(subjects []rbacv1.Subject, group string) bool {
for _, subject := range subjects { for _, subject := range subjects {
if subject.Kind == rbacv1.GroupKind && subject.Name == group { if subject.Kind == rbacv1.GroupKind && subject.Name == group {
return true return true
@@ -1151,9 +1156,3 @@ func containsgroup(subjects []rbacv1.Subject, group string) bool {
} }
return false return false
} }
func workspaceQuery(workspace string) *query.Query {
q := query.New()
q.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1alpha1.WorkspaceLabel, workspace))
return q
}

View File

@@ -1,175 +0,0 @@
/*
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 im
import (
"fmt"
"net/mail"
"github.com/go-ldap/ldap"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
authuser "k8s.io/apiserver/pkg/authentication/user"
"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"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/constants"
)
var (
AuthRateLimitExceeded = fmt.Errorf("auth rate limit exceeded")
AuthFailedIncorrectPassword = fmt.Errorf("incorrect password")
AuthFailedAccountIsNotActive = fmt.Errorf("account is not active")
AuthFailedIdentityMappingNotMatch = fmt.Errorf("identity mapping not match")
)
type PasswordAuthenticator interface {
Authenticate(username, password string) (authuser.Info, error)
}
type passwordAuthenticator struct {
ksClient kubesphere.Interface
userLister iamv1alpha2listers.UserLister
options *authoptions.AuthenticationOptions
}
func NewPasswordAuthenticator(ksClient kubesphere.Interface,
userLister iamv1alpha2listers.UserLister,
options *authoptions.AuthenticationOptions) PasswordAuthenticator {
return &passwordAuthenticator{
ksClient: ksClient,
userLister: userLister,
options: options}
}
func (im *passwordAuthenticator) Authenticate(username, password string) (authuser.Info, error) {
user, err := im.searchUser(username)
if err != nil {
// internal error
if !errors.IsNotFound(err) {
klog.Error(err)
return nil, err
}
}
providerOptions, ldapProvider := im.getLdapProvider()
// no identity provider
// even auth failed, still return username to record login attempt
if user == nil && (providerOptions == nil || providerOptions.MappingMethod != oauth.MappingMethodAuto) {
return nil, AuthFailedIncorrectPassword
}
if user != nil && user.Status.State != iamv1alpha2.UserActive {
if user.Status.State == iamv1alpha2.UserAuthLimitExceeded {
klog.Errorf("%s, username: %s", AuthRateLimitExceeded, username)
return nil, AuthRateLimitExceeded
} else {
klog.Errorf("%s, username: %s", AuthFailedAccountIsNotActive, username)
return nil, AuthFailedAccountIsNotActive
}
}
// able to login using the locally principal admin account and password in case of a disruption of LDAP services.
if ldapProvider != nil && username != constants.AdminUserName {
if providerOptions.MappingMethod == oauth.MappingMethodLookup &&
(user == nil || user.Labels[iamv1alpha2.IdentifyProviderLabel] != providerOptions.Name) {
klog.Error(AuthFailedIdentityMappingNotMatch)
return nil, AuthFailedIdentityMappingNotMatch
}
if providerOptions.MappingMethod == oauth.MappingMethodAuto &&
user != nil && user.Labels[iamv1alpha2.IdentifyProviderLabel] != providerOptions.Name {
klog.Error(AuthFailedIdentityMappingNotMatch)
return nil, AuthFailedIdentityMappingNotMatch
}
authenticated, err := ldapProvider.Authenticate(username, password)
if err != nil {
klog.Error(err)
if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) || ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
return nil, AuthFailedIncorrectPassword
} else {
return nil, err
}
}
if authenticated != nil && user == nil {
authenticated.Labels = map[string]string{iamv1alpha2.IdentifyProviderLabel: providerOptions.Name}
if authenticated, err = im.ksClient.IamV1alpha2().Users().Create(authenticated); err != nil {
klog.Error(err)
return nil, err
}
}
if authenticated != nil {
return &authuser.DefaultInfo{
Name: authenticated.Name,
UID: string(authenticated.UID),
}, nil
}
}
if checkPasswordHash(password, user.Spec.EncryptedPassword) {
return &authuser.DefaultInfo{
Name: user.Name,
UID: string(user.UID),
Groups: user.Spec.Groups,
}, nil
}
return nil, AuthFailedIncorrectPassword
}
func (im *passwordAuthenticator) searchUser(username string) (*iamv1alpha2.User, error) {
if _, err := mail.ParseAddress(username); err != nil {
return im.userLister.Get(username)
} else {
users, err := im.userLister.List(labels.Everything())
if err != nil {
klog.Error(err)
return nil, err
}
for _, find := range users {
if find.Spec.Email == username {
return find, nil
}
}
}
return nil, errors.NewNotFound(iamv1alpha2.Resource("user"), username)
}
func (im *passwordAuthenticator) getLdapProvider() (*oauth.IdentityProviderOptions, identityprovider.LdapProvider) {
for _, options := range im.options.OAuthOptions.IdentityProviders {
if options.Type == identityprovider.LdapIdentityProvider {
if provider, err := identityprovider.NewLdapProvider(options.Provider); err != nil {
klog.Error(err)
} else {
return &options, provider
}
}
}
return nil, nil
}

View File

@@ -16,7 +16,7 @@ limitations under the License.
package im package im
import ( import (
"golang.org/x/crypto/bcrypt" "fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog" "k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
@@ -24,8 +24,8 @@ import (
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/query" "kubesphere.io/kubesphere/pkg/apiserver/query"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned" kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models/auth"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource" resources "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
) )
type IdentityManagementInterface interface { type IdentityManagementInterface interface {
@@ -35,41 +35,40 @@ type IdentityManagementInterface interface {
UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error)
DescribeUser(username string) (*iamv1alpha2.User, error) DescribeUser(username string) (*iamv1alpha2.User, error)
ModifyPassword(username string, password string) error ModifyPassword(username string, password string) error
ListLoginRecords(query *query.Query) (*api.ListResult, error) ListLoginRecords(username string, query *query.Query) (*api.ListResult, error)
PasswordVerify(username string, password string) error PasswordVerify(username string, password string) error
} }
func NewOperator(ksClient kubesphere.Interface, factory informers.InformerFactory, options *authoptions.AuthenticationOptions) IdentityManagementInterface { func NewOperator(ksClient kubesphere.Interface, userGetter resources.Interface, loginRecordGetter resources.Interface, options *authoptions.AuthenticationOptions) IdentityManagementInterface {
im := &defaultIMOperator{ im := &imOperator{
ksClient: ksClient, ksClient: ksClient,
resourceGetter: resourcev1alpha3.NewResourceGetter(factory), userGetter: userGetter,
options: options, loginRecordGetter: loginRecordGetter,
options: options,
} }
return im return im
} }
type defaultIMOperator struct { type imOperator struct {
ksClient kubesphere.Interface ksClient kubesphere.Interface
resourceGetter *resourcev1alpha3.ResourceGetter userGetter resources.Interface
options *authoptions.AuthenticationOptions loginRecordGetter resources.Interface
options *authoptions.AuthenticationOptions
} }
func (im *defaultIMOperator) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) { // UpdateUser returns user information after update.
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", user.Name) func (im *imOperator) UpdateUser(new *iamv1alpha2.User) (*iamv1alpha2.User, error) {
old, err := im.fetch(new.Name)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
if old.Annotations == nil {
old := obj.(*iamv1alpha2.User).DeepCopy() old.Annotations = make(map[string]string, 0)
if user.Annotations == nil {
user.Annotations = make(map[string]string, 0)
} }
user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] = old.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] // keep encrypted password
user.Spec.EncryptedPassword = old.Spec.EncryptedPassword new.Spec.EncryptedPassword = old.Spec.EncryptedPassword
user.Status = old.Status updated, err := im.ksClient.IamV1alpha2().Users().Update(old)
updated, err := im.ksClient.IamV1alpha2().Users().Update(user)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
@@ -77,18 +76,23 @@ func (im *defaultIMOperator) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.Us
return ensurePasswordNotOutput(updated), nil return ensurePasswordNotOutput(updated), nil
} }
func (im *defaultIMOperator) ModifyPassword(username string, password string) error { func (im *imOperator) fetch(username string) (*iamv1alpha2.User, error) {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username) obj, err := im.userGetter.Get("", username)
if err != nil {
klog.Error(err)
return nil, err
}
user := obj.(*iamv1alpha2.User).DeepCopy()
return user, nil
}
func (im *imOperator) ModifyPassword(username string, password string) error {
user, err := im.fetch(username)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return err return err
} }
user := obj.(*iamv1alpha2.User).DeepCopy()
delete(user.Annotations, iamv1alpha2.PasswordEncryptedAnnotation)
user.Spec.EncryptedPassword = password user.Spec.EncryptedPassword = password
_, err = im.ksClient.IamV1alpha2().Users().Update(user) _, err = im.ksClient.IamV1alpha2().Users().Update(user)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
@@ -97,13 +101,12 @@ func (im *defaultIMOperator) ModifyPassword(username string, password string) er
return nil return nil
} }
func (im *defaultIMOperator) ListUsers(query *query.Query) (result *api.ListResult, err error) { func (im *imOperator) ListUsers(query *query.Query) (result *api.ListResult, err error) {
result, err = im.resourceGetter.List(iamv1alpha2.ResourcesPluralUser, "", query) result, err = im.userGetter.List("", query)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
items := make([]interface{}, 0) items := make([]interface{}, 0)
for _, item := range result.Items { for _, item := range result.Items {
user := item.(*iamv1alpha2.User) user := item.(*iamv1alpha2.User)
@@ -114,40 +117,34 @@ func (im *defaultIMOperator) ListUsers(query *query.Query) (result *api.ListResu
return result, nil return result, nil
} }
func (im *defaultIMOperator) PasswordVerify(username string, password string) error { func (im *imOperator) PasswordVerify(username string, password string) error {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username) obj, err := im.userGetter.Get("", username)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return err return err
} }
user := obj.(*iamv1alpha2.User) user := obj.(*iamv1alpha2.User)
if checkPasswordHash(password, user.Spec.EncryptedPassword) { if err = auth.PasswordVerify(user.Spec.EncryptedPassword, password); err != nil {
return nil return err
} }
return AuthFailedIncorrectPassword return nil
} }
func checkPasswordHash(password, hash string) bool { func (im *imOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) obj, err := im.userGetter.Get("", username)
return err == nil
}
func (im *defaultIMOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
user := obj.(*iamv1alpha2.User) user := obj.(*iamv1alpha2.User)
return ensurePasswordNotOutput(user), nil return ensurePasswordNotOutput(user), nil
} }
func (im *defaultIMOperator) DeleteUser(username string) error { func (im *imOperator) DeleteUser(username string) error {
return im.ksClient.IamV1alpha2().Users().Delete(username, metav1.NewDeleteOptions(0)) return im.ksClient.IamV1alpha2().Users().Delete(username, metav1.NewDeleteOptions(0))
} }
func (im *defaultIMOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) { func (im *imOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
user, err := im.ksClient.IamV1alpha2().Users().Create(user) user, err := im.ksClient.IamV1alpha2().Users().Create(user)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
@@ -156,8 +153,9 @@ func (im *defaultIMOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.Us
return user, nil return user, nil
} }
func (im *defaultIMOperator) ListLoginRecords(query *query.Query) (*api.ListResult, error) { func (im *imOperator) ListLoginRecords(username string, q *query.Query) (*api.ListResult, error) {
result, err := im.resourceGetter.List(iamv1alpha2.ResourcesPluralLoginRecord, "", query) q.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", iamv1alpha2.UserReferenceLabel, username))
result, err := im.loginRecordGetter.List("", q)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err

View File

@@ -15,25 +15,3 @@ limitations under the License.
*/ */
package im package im
import (
"golang.org/x/crypto/bcrypt"
"testing"
)
func TestEncryptPassword(t *testing.T) {
password := "P@88w0rd"
encryptedPassword, err := hashPassword(password)
if err != nil {
t.Fatal(err)
}
if !checkPasswordHash(password, encryptedPassword) {
t.Fatal(err)
}
t.Log(encryptedPassword)
}
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
return string(bytes), err
}

View File

@@ -133,9 +133,9 @@ func NewResourceGetter(factory informers.InformerFactory) *ResourceGetter {
} }
} }
// tryResource will retrieve a getter with resource name, it doesn't guarantee find resource with correct group version // TryResource will retrieve a getter with resource name, it doesn't guarantee find resource with correct group version
// need to refactor this use schema.GroupVersionResource // need to refactor this use schema.GroupVersionResource
func (r *ResourceGetter) tryResource(resource string) v1alpha3.Interface { func (r *ResourceGetter) TryResource(resource string) v1alpha3.Interface {
for k, v := range r.getters { for k, v := range r.getters {
if k.Resource == resource { if k.Resource == resource {
return v return v
@@ -145,7 +145,7 @@ func (r *ResourceGetter) tryResource(resource string) v1alpha3.Interface {
} }
func (r *ResourceGetter) Get(resource, namespace, name string) (runtime.Object, error) { func (r *ResourceGetter) Get(resource, namespace, name string) (runtime.Object, error) {
getter := r.tryResource(resource) getter := r.TryResource(resource)
if getter == nil { if getter == nil {
return nil, ErrResourceNotSupported return nil, ErrResourceNotSupported
} }
@@ -153,7 +153,7 @@ func (r *ResourceGetter) Get(resource, namespace, name string) (runtime.Object,
} }
func (r *ResourceGetter) List(resource, namespace string, query *query.Query) (*api.ListResult, error) { func (r *ResourceGetter) List(resource, namespace string, query *query.Query) (*api.ListResult, error) {
getter := r.tryResource(resource) getter := r.TryResource(resource)
if getter == nil { if getter == nil {
return nil, ErrResourceNotSupported return nil, ErrResourceNotSupported
} }

View File

@@ -30,7 +30,7 @@ import (
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
tenantv1alpha2 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha2" tenantv1alpha2 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory" "kubesphere.io/kubesphere/pkg/apiserver/authorization/rbac"
"kubesphere.io/kubesphere/pkg/apiserver/query" "kubesphere.io/kubesphere/pkg/apiserver/query"
fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake" fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/informers"
@@ -540,8 +540,8 @@ func prepare() Interface {
RoleBindings().Informer().GetIndexer().Add(roleBinding) RoleBindings().Informer().GetIndexer().Add(roleBinding)
} }
amOperator := am.NewOperator(fakeInformerFactory, ksClient, k8sClient) amOperator := am.NewOperator(ksClient, k8sClient, fakeInformerFactory)
authorizer := authorizerfactory.NewRBACAuthorizer(amOperator) authorizer := rbac.NewRBACAuthorizer(amOperator)
return New(fakeInformerFactory, k8sClient, ksClient, nil, nil, nil, amOperator, authorizer) return New(fakeInformerFactory, k8sClient, ksClient, nil, nil, nil, amOperator, authorizer)
} }

View File

@@ -33,7 +33,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
urlruntime "k8s.io/apimachinery/pkg/util/runtime" urlruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/klog" "k8s.io/klog"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/informers"
@@ -51,9 +50,7 @@ import (
metricsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2" metricsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2"
tenantv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2" tenantv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2"
terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2" terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/iam/group" "kubesphere.io/kubesphere/pkg/models/iam/group"
"kubesphere.io/kubesphere/pkg/models/iam/im"
fakedevops "kubesphere.io/kubesphere/pkg/simple/client/devops/fake" fakedevops "kubesphere.io/kubesphere/pkg/simple/client/devops/fake"
"kubesphere.io/kubesphere/pkg/simple/client/k8s" "kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix" "kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
@@ -116,12 +113,12 @@ func generateSwaggerJson() []byte {
informerFactory := informers.NewNullInformerFactory() informerFactory := informers.NewNullInformerFactory()
urlruntime.Must(oauth.AddToContainer(container, nil, nil, nil, nil, nil)) urlruntime.Must(oauth.AddToContainer(container, nil, nil, nil, nil, nil, nil))
urlruntime.Must(clusterkapisv1alpha1.AddToContainer(container, informerFactory.KubernetesSharedInformerFactory(), urlruntime.Must(clusterkapisv1alpha1.AddToContainer(container, informerFactory.KubernetesSharedInformerFactory(),
informerFactory.KubeSphereSharedInformerFactory(), "", "", "")) informerFactory.KubeSphereSharedInformerFactory(), "", "", ""))
urlruntime.Must(devopsv1alpha2.AddToContainer(container, informerFactory.KubeSphereSharedInformerFactory(), &fakedevops.Devops{}, nil, clientsets.KubeSphere(), fakes3.NewFakeS3(), "", am.NewReadOnlyOperator(informerFactory))) urlruntime.Must(devopsv1alpha2.AddToContainer(container, informerFactory.KubeSphereSharedInformerFactory(), &fakedevops.Devops{}, nil, clientsets.KubeSphere(), fakes3.NewFakeS3(), "", nil))
urlruntime.Must(devopsv1alpha3.AddToContainer(container, &fakedevops.Devops{}, clientsets.Kubernetes(), clientsets.KubeSphere(), informerFactory.KubeSphereSharedInformerFactory(), informerFactory.KubernetesSharedInformerFactory())) urlruntime.Must(devopsv1alpha3.AddToContainer(container, &fakedevops.Devops{}, clientsets.Kubernetes(), clientsets.KubeSphere(), informerFactory.KubeSphereSharedInformerFactory(), informerFactory.KubernetesSharedInformerFactory()))
urlruntime.Must(iamv1alpha2.AddToContainer(container, im.NewOperator(clientsets.KubeSphere(), informerFactory, nil), am.NewReadOnlyOperator(informerFactory), group.New(informerFactory, clientsets.KubeSphere(), clientsets.Kubernetes()), authoptions.NewAuthenticateOptions())) urlruntime.Must(iamv1alpha2.AddToContainer(container, nil, nil, group.New(informerFactory, clientsets.KubeSphere(), clientsets.Kubernetes()), nil))
urlruntime.Must(monitoringv1alpha3.AddToContainer(container, clientsets.Kubernetes(), nil, informerFactory, nil)) urlruntime.Must(monitoringv1alpha3.AddToContainer(container, clientsets.Kubernetes(), nil, informerFactory, nil))
urlruntime.Must(openpitrixv1.AddToContainer(container, informerFactory, openpitrix.NewMockClient(nil))) urlruntime.Must(openpitrixv1.AddToContainer(container, informerFactory, openpitrix.NewMockClient(nil)))
urlruntime.Must(operationsv1alpha2.AddToContainer(container, clientsets.Kubernetes())) urlruntime.Must(operationsv1alpha2.AddToContainer(container, clientsets.Kubernetes()))

View File

@@ -1,5 +1,4 @@
/* Copyright 2018 gotest.tools authors
Copyright 2020 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -12,11 +11,3 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/
package iam
type PasswordReset struct {
CurrentPassword string `json:"currentPassword"`
Password string `json:"password"`
}

311
vendor/gotest.tools/assert/assert.go vendored Normal file
View File

@@ -0,0 +1,311 @@
/*Package assert provides assertions for comparing expected values to actual
values. When an assertion fails a helpful error message is printed.
Assert and Check
Assert() and Check() both accept a Comparison, and fail the test when the
comparison fails. The one difference is that Assert() will end the test execution
immediately (using t.FailNow()) whereas Check() will fail the test (using t.Fail()),
return the value of the comparison, then proceed with the rest of the test case.
Example usage
The example below shows assert used with some common types.
import (
"testing"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestEverything(t *testing.T) {
// booleans
assert.Assert(t, ok)
assert.Assert(t, !missing)
// primitives
assert.Equal(t, count, 1)
assert.Equal(t, msg, "the message")
assert.Assert(t, total != 10) // NotEqual
// errors
assert.NilError(t, closer.Close())
assert.Error(t, err, "the exact error message")
assert.ErrorContains(t, err, "includes this")
assert.ErrorType(t, err, os.IsNotExist)
// complex types
assert.DeepEqual(t, result, myStruct{Name: "title"})
assert.Assert(t, is.Len(items, 3))
assert.Assert(t, len(sequence) != 0) // NotEmpty
assert.Assert(t, is.Contains(mapping, "key"))
// pointers and interface
assert.Assert(t, is.Nil(ref))
assert.Assert(t, ref != nil) // NotNil
}
Comparisons
Package https://godoc.org/gotest.tools/assert/cmp provides
many common comparisons. Additional comparisons can be written to compare
values in other ways. See the example Assert (CustomComparison).
Automated migration from testify
gty-migrate-from-testify is a binary which can update source code which uses
testify assertions to use the assertions provided by this package.
See http://bit.do/cmd-gty-migrate-from-testify.
*/
package assert // import "gotest.tools/assert"
import (
"fmt"
"go/ast"
"go/token"
gocmp "github.com/google/go-cmp/cmp"
"gotest.tools/assert/cmp"
"gotest.tools/internal/format"
"gotest.tools/internal/source"
)
// BoolOrComparison can be a bool, or cmp.Comparison. See Assert() for usage.
type BoolOrComparison interface{}
// TestingT is the subset of testing.T used by the assert package.
type TestingT interface {
FailNow()
Fail()
Log(args ...interface{})
}
type helperT interface {
Helper()
}
const failureMessage = "assertion failed: "
// nolint: gocyclo
func assert(
t TestingT,
failer func(),
argSelector argSelector,
comparison BoolOrComparison,
msgAndArgs ...interface{},
) bool {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
var success bool
switch check := comparison.(type) {
case bool:
if check {
return true
}
logFailureFromBool(t, msgAndArgs...)
// Undocumented legacy comparison without Result type
case func() (success bool, message string):
success = runCompareFunc(t, check, msgAndArgs...)
case nil:
return true
case error:
msg := "error is not nil: "
t.Log(format.WithCustomMessage(failureMessage+msg+check.Error(), msgAndArgs...))
case cmp.Comparison:
success = runComparison(t, argSelector, check, msgAndArgs...)
case func() cmp.Result:
success = runComparison(t, argSelector, check, msgAndArgs...)
default:
t.Log(fmt.Sprintf("invalid Comparison: %v (%T)", check, check))
}
if success {
return true
}
failer()
return false
}
func runCompareFunc(
t TestingT,
f func() (success bool, message string),
msgAndArgs ...interface{},
) bool {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
if success, message := f(); !success {
t.Log(format.WithCustomMessage(failureMessage+message, msgAndArgs...))
return false
}
return true
}
func logFailureFromBool(t TestingT, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
const stackIndex = 3 // Assert()/Check(), assert(), formatFailureFromBool()
const comparisonArgPos = 1
args, err := source.CallExprArgs(stackIndex)
if err != nil {
t.Log(err.Error())
return
}
msg, err := boolFailureMessage(args[comparisonArgPos])
if err != nil {
t.Log(err.Error())
msg = "expression is false"
}
t.Log(format.WithCustomMessage(failureMessage+msg, msgAndArgs...))
}
func boolFailureMessage(expr ast.Expr) (string, error) {
if binaryExpr, ok := expr.(*ast.BinaryExpr); ok && binaryExpr.Op == token.NEQ {
x, err := source.FormatNode(binaryExpr.X)
if err != nil {
return "", err
}
y, err := source.FormatNode(binaryExpr.Y)
if err != nil {
return "", err
}
return x + " is " + y, nil
}
if unaryExpr, ok := expr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.NOT {
x, err := source.FormatNode(unaryExpr.X)
if err != nil {
return "", err
}
return x + " is true", nil
}
formatted, err := source.FormatNode(expr)
if err != nil {
return "", err
}
return "expression is false: " + formatted, nil
}
// Assert performs a comparison. If the comparison fails the test is marked as
// failed, a failure message is logged, and execution is stopped immediately.
//
// The comparison argument may be one of three types: bool, cmp.Comparison or
// error.
// When called with a bool the failure message will contain the literal source
// code of the expression.
// When called with a cmp.Comparison the comparison is responsible for producing
// a helpful failure message.
// When called with an error a nil value is considered success. A non-nil error
// is a failure, and Error() is used as the failure message.
func Assert(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert(t, t.FailNow, argsFromComparisonCall, comparison, msgAndArgs...)
}
// Check performs a comparison. If the comparison fails the test is marked as
// failed, a failure message is logged, and Check returns false. Otherwise returns
// true.
//
// See Assert for details about the comparison arg and failure messages.
func Check(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) bool {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
return assert(t, t.Fail, argsFromComparisonCall, comparison, msgAndArgs...)
}
// NilError fails the test immediately if err is not nil.
// This is equivalent to Assert(t, err)
func NilError(t TestingT, err error, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert(t, t.FailNow, argsAfterT, err, msgAndArgs...)
}
// Equal uses the == operator to assert two values are equal and fails the test
// if they are not equal.
//
// If the comparison fails Equal will use the variable names for x and y as part
// of the failure message to identify the actual and expected values.
//
// If either x or y are a multi-line string the failure message will include a
// unified diff of the two values. If the values only differ by whitespace
// the unified diff will be augmented by replacing whitespace characters with
// visible characters to identify the whitespace difference.
//
// This is equivalent to Assert(t, cmp.Equal(x, y)).
func Equal(t TestingT, x, y interface{}, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert(t, t.FailNow, argsAfterT, cmp.Equal(x, y), msgAndArgs...)
}
// DeepEqual uses google/go-cmp (http://bit.do/go-cmp) to assert two values are
// equal and fails the test if they are not equal.
//
// Package https://godoc.org/gotest.tools/assert/opt provides some additional
// commonly used Options.
//
// This is equivalent to Assert(t, cmp.DeepEqual(x, y)).
func DeepEqual(t TestingT, x, y interface{}, opts ...gocmp.Option) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert(t, t.FailNow, argsAfterT, cmp.DeepEqual(x, y, opts...))
}
// Error fails the test if err is nil, or the error message is not the expected
// message.
// Equivalent to Assert(t, cmp.Error(err, message)).
func Error(t TestingT, err error, message string, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert(t, t.FailNow, argsAfterT, cmp.Error(err, message), msgAndArgs...)
}
// ErrorContains fails the test if err is nil, or the error message does not
// contain the expected substring.
// Equivalent to Assert(t, cmp.ErrorContains(err, substring)).
func ErrorContains(t TestingT, err error, substring string, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert(t, t.FailNow, argsAfterT, cmp.ErrorContains(err, substring), msgAndArgs...)
}
// ErrorType fails the test if err is nil, or err is not the expected type.
//
// Expected can be one of:
// a func(error) bool which returns true if the error is the expected type,
// an instance of (or a pointer to) a struct of the expected type,
// a pointer to an interface the error is expected to implement,
// a reflect.Type of the expected struct or interface.
//
// Equivalent to Assert(t, cmp.ErrorType(err, expected)).
func ErrorType(t TestingT, err error, expected interface{}, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert(t, t.FailNow, argsAfterT, cmp.ErrorType(err, expected), msgAndArgs...)
}

View File

@@ -0,0 +1,356 @@
/*Package cmp provides Comparisons for Assert and Check*/
package cmp // import "gotest.tools/assert/cmp"
import (
"fmt"
"reflect"
"regexp"
"strings"
"github.com/google/go-cmp/cmp"
"gotest.tools/internal/format"
)
// Comparison is a function which compares values and returns ResultSuccess if
// the actual value matches the expected value. If the values do not match the
// Result will contain a message about why it failed.
type Comparison func() Result
// DeepEqual compares two values using google/go-cmp (http://bit.do/go-cmp)
// and succeeds if the values are equal.
//
// The comparison can be customized using comparison Options.
// Package https://godoc.org/gotest.tools/assert/opt provides some additional
// commonly used Options.
func DeepEqual(x, y interface{}, opts ...cmp.Option) Comparison {
return func() (result Result) {
defer func() {
if panicmsg, handled := handleCmpPanic(recover()); handled {
result = ResultFailure(panicmsg)
}
}()
diff := cmp.Diff(x, y, opts...)
if diff == "" {
return ResultSuccess
}
return multiLineDiffResult(diff)
}
}
func handleCmpPanic(r interface{}) (string, bool) {
if r == nil {
return "", false
}
panicmsg, ok := r.(string)
if !ok {
panic(r)
}
switch {
case strings.HasPrefix(panicmsg, "cannot handle unexported field"):
return panicmsg, true
}
panic(r)
}
func toResult(success bool, msg string) Result {
if success {
return ResultSuccess
}
return ResultFailure(msg)
}
// RegexOrPattern may be either a *regexp.Regexp or a string that is a valid
// regexp pattern.
type RegexOrPattern interface{}
// Regexp succeeds if value v matches regular expression re.
//
// Example:
// assert.Assert(t, cmp.Regexp("^[0-9a-f]{32}$", str))
// r := regexp.MustCompile("^[0-9a-f]{32}$")
// assert.Assert(t, cmp.Regexp(r, str))
func Regexp(re RegexOrPattern, v string) Comparison {
match := func(re *regexp.Regexp) Result {
return toResult(
re.MatchString(v),
fmt.Sprintf("value %q does not match regexp %q", v, re.String()))
}
return func() Result {
switch regex := re.(type) {
case *regexp.Regexp:
return match(regex)
case string:
re, err := regexp.Compile(regex)
if err != nil {
return ResultFailure(err.Error())
}
return match(re)
default:
return ResultFailure(fmt.Sprintf("invalid type %T for regex pattern", regex))
}
}
}
// Equal succeeds if x == y. See assert.Equal for full documentation.
func Equal(x, y interface{}) Comparison {
return func() Result {
switch {
case x == y:
return ResultSuccess
case isMultiLineStringCompare(x, y):
diff := format.UnifiedDiff(format.DiffConfig{A: x.(string), B: y.(string)})
return multiLineDiffResult(diff)
}
return ResultFailureTemplate(`
{{- .Data.x}} (
{{- with callArg 0 }}{{ formatNode . }} {{end -}}
{{- printf "%T" .Data.x -}}
) != {{ .Data.y}} (
{{- with callArg 1 }}{{ formatNode . }} {{end -}}
{{- printf "%T" .Data.y -}}
)`,
map[string]interface{}{"x": x, "y": y})
}
}
func isMultiLineStringCompare(x, y interface{}) bool {
strX, ok := x.(string)
if !ok {
return false
}
strY, ok := y.(string)
if !ok {
return false
}
return strings.Contains(strX, "\n") || strings.Contains(strY, "\n")
}
func multiLineDiffResult(diff string) Result {
return ResultFailureTemplate(`
--- {{ with callArg 0 }}{{ formatNode . }}{{else}}{{end}}
+++ {{ with callArg 1 }}{{ formatNode . }}{{else}}{{end}}
{{ .Data.diff }}`,
map[string]interface{}{"diff": diff})
}
// Len succeeds if the sequence has the expected length.
func Len(seq interface{}, expected int) Comparison {
return func() (result Result) {
defer func() {
if e := recover(); e != nil {
result = ResultFailure(fmt.Sprintf("type %T does not have a length", seq))
}
}()
value := reflect.ValueOf(seq)
length := value.Len()
if length == expected {
return ResultSuccess
}
msg := fmt.Sprintf("expected %s (length %d) to have length %d", seq, length, expected)
return ResultFailure(msg)
}
}
// Contains succeeds if item is in collection. Collection may be a string, map,
// slice, or array.
//
// If collection is a string, item must also be a string, and is compared using
// strings.Contains().
// If collection is a Map, contains will succeed if item is a key in the map.
// If collection is a slice or array, item is compared to each item in the
// sequence using reflect.DeepEqual().
func Contains(collection interface{}, item interface{}) Comparison {
return func() Result {
colValue := reflect.ValueOf(collection)
if !colValue.IsValid() {
return ResultFailure(fmt.Sprintf("nil does not contain items"))
}
msg := fmt.Sprintf("%v does not contain %v", collection, item)
itemValue := reflect.ValueOf(item)
switch colValue.Type().Kind() {
case reflect.String:
if itemValue.Type().Kind() != reflect.String {
return ResultFailure("string may only contain strings")
}
return toResult(
strings.Contains(colValue.String(), itemValue.String()),
fmt.Sprintf("string %q does not contain %q", collection, item))
case reflect.Map:
if itemValue.Type() != colValue.Type().Key() {
return ResultFailure(fmt.Sprintf(
"%v can not contain a %v key", colValue.Type(), itemValue.Type()))
}
return toResult(colValue.MapIndex(itemValue).IsValid(), msg)
case reflect.Slice, reflect.Array:
for i := 0; i < colValue.Len(); i++ {
if reflect.DeepEqual(colValue.Index(i).Interface(), item) {
return ResultSuccess
}
}
return ResultFailure(msg)
default:
return ResultFailure(fmt.Sprintf("type %T does not contain items", collection))
}
}
}
// Panics succeeds if f() panics.
func Panics(f func()) Comparison {
return func() (result Result) {
defer func() {
if err := recover(); err != nil {
result = ResultSuccess
}
}()
f()
return ResultFailure("did not panic")
}
}
// Error succeeds if err is a non-nil error, and the error message equals the
// expected message.
func Error(err error, message string) Comparison {
return func() Result {
switch {
case err == nil:
return ResultFailure("expected an error, got nil")
case err.Error() != message:
return ResultFailure(fmt.Sprintf(
"expected error %q, got %s", message, formatErrorMessage(err)))
}
return ResultSuccess
}
}
// ErrorContains succeeds if err is a non-nil error, and the error message contains
// the expected substring.
func ErrorContains(err error, substring string) Comparison {
return func() Result {
switch {
case err == nil:
return ResultFailure("expected an error, got nil")
case !strings.Contains(err.Error(), substring):
return ResultFailure(fmt.Sprintf(
"expected error to contain %q, got %s", substring, formatErrorMessage(err)))
}
return ResultSuccess
}
}
func formatErrorMessage(err error) string {
if _, ok := err.(interface {
Cause() error
}); ok {
return fmt.Sprintf("%q\n%+v", err, err)
}
// This error was not wrapped with github.com/pkg/errors
return fmt.Sprintf("%q", err)
}
// Nil succeeds if obj is a nil interface, pointer, or function.
//
// Use NilError() for comparing errors. Use Len(obj, 0) for comparing slices,
// maps, and channels.
func Nil(obj interface{}) Comparison {
msgFunc := func(value reflect.Value) string {
return fmt.Sprintf("%v (type %s) is not nil", reflect.Indirect(value), value.Type())
}
return isNil(obj, msgFunc)
}
func isNil(obj interface{}, msgFunc func(reflect.Value) string) Comparison {
return func() Result {
if obj == nil {
return ResultSuccess
}
value := reflect.ValueOf(obj)
kind := value.Type().Kind()
if kind >= reflect.Chan && kind <= reflect.Slice {
if value.IsNil() {
return ResultSuccess
}
return ResultFailure(msgFunc(value))
}
return ResultFailure(fmt.Sprintf("%v (type %s) can not be nil", value, value.Type()))
}
}
// ErrorType succeeds if err is not nil and is of the expected type.
//
// Expected can be one of:
// a func(error) bool which returns true if the error is the expected type,
// an instance of (or a pointer to) a struct of the expected type,
// a pointer to an interface the error is expected to implement,
// a reflect.Type of the expected struct or interface.
func ErrorType(err error, expected interface{}) Comparison {
return func() Result {
switch expectedType := expected.(type) {
case func(error) bool:
return cmpErrorTypeFunc(err, expectedType)
case reflect.Type:
if expectedType.Kind() == reflect.Interface {
return cmpErrorTypeImplementsType(err, expectedType)
}
return cmpErrorTypeEqualType(err, expectedType)
case nil:
return ResultFailure(fmt.Sprintf("invalid type for expected: nil"))
}
expectedType := reflect.TypeOf(expected)
switch {
case expectedType.Kind() == reflect.Struct, isPtrToStruct(expectedType):
return cmpErrorTypeEqualType(err, expectedType)
case isPtrToInterface(expectedType):
return cmpErrorTypeImplementsType(err, expectedType.Elem())
}
return ResultFailure(fmt.Sprintf("invalid type for expected: %T", expected))
}
}
func cmpErrorTypeFunc(err error, f func(error) bool) Result {
if f(err) {
return ResultSuccess
}
actual := "nil"
if err != nil {
actual = fmt.Sprintf("%s (%T)", err, err)
}
return ResultFailureTemplate(`error is {{ .Data.actual }}
{{- with callArg 1 }}, not {{ formatNode . }}{{end -}}`,
map[string]interface{}{"actual": actual})
}
func cmpErrorTypeEqualType(err error, expectedType reflect.Type) Result {
if err == nil {
return ResultFailure(fmt.Sprintf("error is nil, not %s", expectedType))
}
errValue := reflect.ValueOf(err)
if errValue.Type() == expectedType {
return ResultSuccess
}
return ResultFailure(fmt.Sprintf("error is %s (%T), not %s", err, err, expectedType))
}
func cmpErrorTypeImplementsType(err error, expectedType reflect.Type) Result {
if err == nil {
return ResultFailure(fmt.Sprintf("error is nil, not %s", expectedType))
}
errValue := reflect.ValueOf(err)
if errValue.Type().Implements(expectedType) {
return ResultSuccess
}
return ResultFailure(fmt.Sprintf("error is %s (%T), not %s", err, err, expectedType))
}
func isPtrToInterface(typ reflect.Type) bool {
return typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Interface
}
func isPtrToStruct(typ reflect.Type) bool {
return typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct
}

View File

@@ -0,0 +1,94 @@
package cmp
import (
"bytes"
"fmt"
"go/ast"
"text/template"
"gotest.tools/internal/source"
)
// Result of a Comparison.
type Result interface {
Success() bool
}
type result struct {
success bool
message string
}
func (r result) Success() bool {
return r.success
}
func (r result) FailureMessage() string {
return r.message
}
// ResultSuccess is a constant which is returned by a ComparisonWithResult to
// indicate success.
var ResultSuccess = result{success: true}
// ResultFailure returns a failed Result with a failure message.
func ResultFailure(message string) Result {
return result{message: message}
}
// ResultFromError returns ResultSuccess if err is nil. Otherwise ResultFailure
// is returned with the error message as the failure message.
func ResultFromError(err error) Result {
if err == nil {
return ResultSuccess
}
return ResultFailure(err.Error())
}
type templatedResult struct {
success bool
template string
data map[string]interface{}
}
func (r templatedResult) Success() bool {
return r.success
}
func (r templatedResult) FailureMessage(args []ast.Expr) string {
msg, err := renderMessage(r, args)
if err != nil {
return fmt.Sprintf("failed to render failure message: %s", err)
}
return msg
}
// ResultFailureTemplate returns a Result with a template string and data which
// can be used to format a failure message. The template may access data from .Data,
// the comparison args with the callArg function, and the formatNode function may
// be used to format the call args.
func ResultFailureTemplate(template string, data map[string]interface{}) Result {
return templatedResult{template: template, data: data}
}
func renderMessage(result templatedResult, args []ast.Expr) (string, error) {
tmpl := template.New("failure").Funcs(template.FuncMap{
"formatNode": source.FormatNode,
"callArg": func(index int) ast.Expr {
if index >= len(args) {
return nil
}
return args[index]
},
})
var err error
tmpl, err = tmpl.Parse(result.template)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
err = tmpl.Execute(buf, map[string]interface{}{
"Data": result.data,
})
return buf.String(), err
}

106
vendor/gotest.tools/assert/result.go vendored Normal file
View File

@@ -0,0 +1,106 @@
package assert
import (
"fmt"
"go/ast"
"gotest.tools/assert/cmp"
"gotest.tools/internal/format"
"gotest.tools/internal/source"
)
func runComparison(
t TestingT,
argSelector argSelector,
f cmp.Comparison,
msgAndArgs ...interface{},
) bool {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
result := f()
if result.Success() {
return true
}
var message string
switch typed := result.(type) {
case resultWithComparisonArgs:
const stackIndex = 3 // Assert/Check, assert, runComparison
args, err := source.CallExprArgs(stackIndex)
if err != nil {
t.Log(err.Error())
}
message = typed.FailureMessage(filterPrintableExpr(argSelector(args)))
case resultBasic:
message = typed.FailureMessage()
default:
message = fmt.Sprintf("comparison returned invalid Result type: %T", result)
}
t.Log(format.WithCustomMessage(failureMessage+message, msgAndArgs...))
return false
}
type resultWithComparisonArgs interface {
FailureMessage(args []ast.Expr) string
}
type resultBasic interface {
FailureMessage() string
}
// filterPrintableExpr filters the ast.Expr slice to only include Expr that are
// easy to read when printed and contain relevant information to an assertion.
//
// Ident and SelectorExpr are included because they print nicely and the variable
// names may provide additional context to their values.
// BasicLit and CompositeLit are excluded because their source is equivalent to
// their value, which is already available.
// Other types are ignored for now, but could be added if they are relevant.
func filterPrintableExpr(args []ast.Expr) []ast.Expr {
result := make([]ast.Expr, len(args))
for i, arg := range args {
if isShortPrintableExpr(arg) {
result[i] = arg
continue
}
if starExpr, ok := arg.(*ast.StarExpr); ok {
result[i] = starExpr.X
continue
}
}
return result
}
func isShortPrintableExpr(expr ast.Expr) bool {
switch expr.(type) {
case *ast.Ident, *ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr:
return true
case *ast.BinaryExpr, *ast.UnaryExpr:
return true
default:
// CallExpr, ParenExpr, TypeAssertExpr, KeyValueExpr, StarExpr
return false
}
}
type argSelector func([]ast.Expr) []ast.Expr
func argsAfterT(args []ast.Expr) []ast.Expr {
if len(args) < 1 {
return nil
}
return args[1:]
}
func argsFromComparisonCall(args []ast.Expr) []ast.Expr {
if len(args) < 1 {
return nil
}
if callExpr, ok := args[1].(*ast.CallExpr); ok {
return callExpr.Args
}
return nil
}

View File

@@ -0,0 +1,27 @@
Copyright (c) 2013, Patrick Mezard
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,423 @@
/*Package difflib is a partial port of Python difflib module.
Original source: https://github.com/pmezard/go-difflib
This file is trimmed to only the parts used by this repository.
*/
package difflib // import "gotest.tools/internal/difflib"
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
// Match stores line numbers of size of match
type Match struct {
A int
B int
Size int
}
// OpCode identifies the type of diff
type OpCode struct {
Tag byte
I1 int
I2 int
J1 int
J2 int
}
// SequenceMatcher compares sequence of strings. The basic
// algorithm predates, and is a little fancier than, an algorithm
// published in the late 1980's by Ratcliff and Obershelp under the
// hyperbolic name "gestalt pattern matching". The basic idea is to find
// the longest contiguous matching subsequence that contains no "junk"
// elements (R-O doesn't address junk). The same idea is then applied
// recursively to the pieces of the sequences to the left and to the right
// of the matching subsequence. This does not yield minimal edit
// sequences, but does tend to yield matches that "look right" to people.
//
// SequenceMatcher tries to compute a "human-friendly diff" between two
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
// longest *contiguous* & junk-free matching subsequence. That's what
// catches peoples' eyes. The Windows(tm) windiff has another interesting
// notion, pairing up elements that appear uniquely in each sequence.
// That, and the method here, appear to yield more intuitive difference
// reports than does diff. This method appears to be the least vulnerable
// to synching up on blocks of "junk lines", though (like blank lines in
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
// because this is the only method of the 3 that has a *concept* of
// "junk" <wink>.
//
// Timing: Basic R-O is cubic time worst case and quadratic time expected
// case. SequenceMatcher is quadratic time for the worst case and has
// expected-case behavior dependent in a complicated way on how many
// elements the sequences have in common; best case time is linear.
type SequenceMatcher struct {
a []string
b []string
b2j map[string][]int
IsJunk func(string) bool
autoJunk bool
bJunk map[string]struct{}
matchingBlocks []Match
fullBCount map[string]int
bPopular map[string]struct{}
opCodes []OpCode
}
// NewMatcher returns a new SequenceMatcher
func NewMatcher(a, b []string) *SequenceMatcher {
m := SequenceMatcher{autoJunk: true}
m.SetSeqs(a, b)
return &m
}
// SetSeqs sets two sequences to be compared.
func (m *SequenceMatcher) SetSeqs(a, b []string) {
m.SetSeq1(a)
m.SetSeq2(b)
}
// SetSeq1 sets the first sequence to be compared. The second sequence to be compared is
// not changed.
//
// SequenceMatcher computes and caches detailed information about the second
// sequence, so if you want to compare one sequence S against many sequences,
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
// sequences.
//
// See also SetSeqs() and SetSeq2().
func (m *SequenceMatcher) SetSeq1(a []string) {
if &a == &m.a {
return
}
m.a = a
m.matchingBlocks = nil
m.opCodes = nil
}
// SetSeq2 sets the second sequence to be compared. The first sequence to be compared is
// not changed.
func (m *SequenceMatcher) SetSeq2(b []string) {
if &b == &m.b {
return
}
m.b = b
m.matchingBlocks = nil
m.opCodes = nil
m.fullBCount = nil
m.chainB()
}
func (m *SequenceMatcher) chainB() {
// Populate line -> index mapping
b2j := map[string][]int{}
for i, s := range m.b {
indices := b2j[s]
indices = append(indices, i)
b2j[s] = indices
}
// Purge junk elements
m.bJunk = map[string]struct{}{}
if m.IsJunk != nil {
junk := m.bJunk
for s := range b2j {
if m.IsJunk(s) {
junk[s] = struct{}{}
}
}
for s := range junk {
delete(b2j, s)
}
}
// Purge remaining popular elements
popular := map[string]struct{}{}
n := len(m.b)
if m.autoJunk && n >= 200 {
ntest := n/100 + 1
for s, indices := range b2j {
if len(indices) > ntest {
popular[s] = struct{}{}
}
}
for s := range popular {
delete(b2j, s)
}
}
m.bPopular = popular
m.b2j = b2j
}
func (m *SequenceMatcher) isBJunk(s string) bool {
_, ok := m.bJunk[s]
return ok
}
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
//
// If IsJunk is not defined:
//
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
// alo <= i <= i+k <= ahi
// blo <= j <= j+k <= bhi
// and for all (i',j',k') meeting those conditions,
// k >= k'
// i <= i'
// and if i == i', j <= j'
//
// In other words, of all maximal matching blocks, return one that
// starts earliest in a, and of all those maximal matching blocks that
// start earliest in a, return the one that starts earliest in b.
//
// If IsJunk is defined, first the longest matching block is
// determined as above, but with the additional restriction that no
// junk element appears in the block. Then that block is extended as
// far as possible by matching (only) junk elements on both sides. So
// the resulting block never matches on junk except as identical junk
// happens to be adjacent to an "interesting" match.
//
// If no blocks match, return (alo, blo, 0).
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
// CAUTION: stripping common prefix or suffix would be incorrect.
// E.g.,
// ab
// acab
// Longest matching block is "ab", but if common prefix is
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
// strip, so ends up claiming that ab is changed to acab by
// inserting "ca" in the middle. That's minimal but unintuitive:
// "it's obvious" that someone inserted "ac" at the front.
// Windiff ends up at the same place as diff, but by pairing up
// the unique 'b's and then matching the first two 'a's.
besti, bestj, bestsize := alo, blo, 0
// find longest junk-free match
// during an iteration of the loop, j2len[j] = length of longest
// junk-free match ending with a[i-1] and b[j]
j2len := map[int]int{}
for i := alo; i != ahi; i++ {
// look at all instances of a[i] in b; note that because
// b2j has no junk keys, the loop is skipped if a[i] is junk
newj2len := map[int]int{}
for _, j := range m.b2j[m.a[i]] {
// a[i] matches b[j]
if j < blo {
continue
}
if j >= bhi {
break
}
k := j2len[j-1] + 1
newj2len[j] = k
if k > bestsize {
besti, bestj, bestsize = i-k+1, j-k+1, k
}
}
j2len = newj2len
}
// Extend the best by non-junk elements on each end. In particular,
// "popular" non-junk elements aren't in b2j, which greatly speeds
// the inner loop above, but also means "the best" match so far
// doesn't contain any junk *or* popular non-junk elements.
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
!m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
// Now that we have a wholly interesting match (albeit possibly
// empty!), we may as well suck up the matching junk on each
// side of it too. Can't think of a good reason not to, and it
// saves post-processing the (possibly considerable) expense of
// figuring out what to do with it. In the case of an empty
// interesting match, this is clearly the right thing to do,
// because no other kind of match is possible in the regions.
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
return Match{A: besti, B: bestj, Size: bestsize}
}
// GetMatchingBlocks returns a list of triples describing matching subsequences.
//
// Each triple is of the form (i, j, n), and means that
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
// adjacent triples in the list, and the second is not the last triple in the
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
// adjacent equal blocks.
//
// The last triple is a dummy, (len(a), len(b), 0), and is the only
// triple with n==0.
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
if m.matchingBlocks != nil {
return m.matchingBlocks
}
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
match := m.findLongestMatch(alo, ahi, blo, bhi)
i, j, k := match.A, match.B, match.Size
if match.Size > 0 {
if alo < i && blo < j {
matched = matchBlocks(alo, i, blo, j, matched)
}
matched = append(matched, match)
if i+k < ahi && j+k < bhi {
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
}
}
return matched
}
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
// It's possible that we have adjacent equal blocks in the
// matching_blocks list now.
nonAdjacent := []Match{}
i1, j1, k1 := 0, 0, 0
for _, b := range matched {
// Is this block adjacent to i1, j1, k1?
i2, j2, k2 := b.A, b.B, b.Size
if i1+k1 == i2 && j1+k1 == j2 {
// Yes, so collapse them -- this just increases the length of
// the first block by the length of the second, and the first
// block so lengthened remains the block to compare against.
k1 += k2
} else {
// Not adjacent. Remember the first block (k1==0 means it's
// the dummy we started with), and make the second block the
// new block to compare against.
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
i1, j1, k1 = i2, j2, k2
}
}
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
m.matchingBlocks = nonAdjacent
return m.matchingBlocks
}
// GetOpCodes returns a list of 5-tuples describing how to turn a into b.
//
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
// tuple preceding it, and likewise for j1 == the previous j2.
//
// The tags are characters, with these meanings:
//
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
//
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
//
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
//
// 'e' (equal): a[i1:i2] == b[j1:j2]
func (m *SequenceMatcher) GetOpCodes() []OpCode {
if m.opCodes != nil {
return m.opCodes
}
i, j := 0, 0
matching := m.GetMatchingBlocks()
opCodes := make([]OpCode, 0, len(matching))
for _, m := range matching {
// invariant: we've pumped out correct diffs to change
// a[:i] into b[:j], and the next matching block is
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
// out a diff to change a[i:ai] into b[j:bj], pump out
// the matching block, and move (i,j) beyond the match
ai, bj, size := m.A, m.B, m.Size
tag := byte(0)
if i < ai && j < bj {
tag = 'r'
} else if i < ai {
tag = 'd'
} else if j < bj {
tag = 'i'
}
if tag > 0 {
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
}
i, j = ai+size, bj+size
// the list of matching blocks is terminated by a
// sentinel with size 0
if size > 0 {
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
}
}
m.opCodes = opCodes
return m.opCodes
}
// GetGroupedOpCodes isolates change clusters by eliminating ranges with no changes.
//
// Return a generator of groups with up to n lines of context.
// Each group is in the same format as returned by GetOpCodes().
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
if n < 0 {
n = 3
}
codes := m.GetOpCodes()
if len(codes) == 0 {
codes = []OpCode{{'e', 0, 1, 0, 1}}
}
// Fixup leading and trailing groups if they show no changes.
if codes[0].Tag == 'e' {
c := codes[0]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
}
if codes[len(codes)-1].Tag == 'e' {
c := codes[len(codes)-1]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
}
nn := n + n
groups := [][]OpCode{}
group := []OpCode{}
for _, c := range codes {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
// End the current group and start a new one whenever
// there is a large range with no changes.
if c.Tag == 'e' && i2-i1 > nn {
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
j1, min(j2, j1+n)})
groups = append(groups, group)
group = []OpCode{}
i1, j1 = max(i1, i2-n), max(j1, j2-n)
}
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
}
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
groups = append(groups, group)
}
return groups
}

View File

@@ -0,0 +1,161 @@
package format
import (
"bytes"
"fmt"
"strings"
"unicode"
"gotest.tools/internal/difflib"
)
const (
contextLines = 2
)
// DiffConfig for a unified diff
type DiffConfig struct {
A string
B string
From string
To string
}
// UnifiedDiff is a modified version of difflib.WriteUnifiedDiff with better
// support for showing the whitespace differences.
func UnifiedDiff(conf DiffConfig) string {
a := strings.SplitAfter(conf.A, "\n")
b := strings.SplitAfter(conf.B, "\n")
groups := difflib.NewMatcher(a, b).GetGroupedOpCodes(contextLines)
if len(groups) == 0 {
return ""
}
buf := new(bytes.Buffer)
writeFormat := func(format string, args ...interface{}) {
buf.WriteString(fmt.Sprintf(format, args...))
}
writeLine := func(prefix string, s string) {
buf.WriteString(prefix + s)
}
if hasWhitespaceDiffLines(groups, a, b) {
writeLine = visibleWhitespaceLine(writeLine)
}
formatHeader(writeFormat, conf)
for _, group := range groups {
formatRangeLine(writeFormat, group)
for _, opCode := range group {
in, out := a[opCode.I1:opCode.I2], b[opCode.J1:opCode.J2]
switch opCode.Tag {
case 'e':
formatLines(writeLine, " ", in)
case 'r':
formatLines(writeLine, "-", in)
formatLines(writeLine, "+", out)
case 'd':
formatLines(writeLine, "-", in)
case 'i':
formatLines(writeLine, "+", out)
}
}
}
return buf.String()
}
// hasWhitespaceDiffLines returns true if any diff groups is only different
// because of whitespace characters.
func hasWhitespaceDiffLines(groups [][]difflib.OpCode, a, b []string) bool {
for _, group := range groups {
in, out := new(bytes.Buffer), new(bytes.Buffer)
for _, opCode := range group {
if opCode.Tag == 'e' {
continue
}
for _, line := range a[opCode.I1:opCode.I2] {
in.WriteString(line)
}
for _, line := range b[opCode.J1:opCode.J2] {
out.WriteString(line)
}
}
if removeWhitespace(in.String()) == removeWhitespace(out.String()) {
return true
}
}
return false
}
func removeWhitespace(s string) string {
var result []rune
for _, r := range s {
if !unicode.IsSpace(r) {
result = append(result, r)
}
}
return string(result)
}
func visibleWhitespaceLine(ws func(string, string)) func(string, string) {
mapToVisibleSpace := func(r rune) rune {
switch r {
case '\n':
case ' ':
return '·'
case '\t':
return '▷'
case '\v':
return '▽'
case '\r':
return '↵'
case '\f':
return '↓'
default:
if unicode.IsSpace(r) {
return '<27>'
}
}
return r
}
return func(prefix, s string) {
ws(prefix, strings.Map(mapToVisibleSpace, s))
}
}
func formatHeader(wf func(string, ...interface{}), conf DiffConfig) {
if conf.From != "" || conf.To != "" {
wf("--- %s\n", conf.From)
wf("+++ %s\n", conf.To)
}
}
func formatRangeLine(wf func(string, ...interface{}), group []difflib.OpCode) {
first, last := group[0], group[len(group)-1]
range1 := formatRangeUnified(first.I1, last.I2)
range2 := formatRangeUnified(first.J1, last.J2)
wf("@@ -%s +%s @@\n", range1, range2)
}
// Convert range to the "ed" format
func formatRangeUnified(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 1 {
return fmt.Sprintf("%d", beginning)
}
if length == 0 {
beginning-- // empty ranges begin at line just before the range
}
return fmt.Sprintf("%d,%d", beginning, length)
}
func formatLines(writeLine func(string, string), prefix string, lines []string) {
for _, line := range lines {
writeLine(prefix, line)
}
// Add a newline if the last line is missing one so that the diff displays
// properly.
if !strings.HasSuffix(lines[len(lines)-1], "\n") {
writeLine("", "\n")
}
}

View File

@@ -0,0 +1,27 @@
package format // import "gotest.tools/internal/format"
import "fmt"
// Message accepts a msgAndArgs varargs and formats it using fmt.Sprintf
func Message(msgAndArgs ...interface{}) string {
switch len(msgAndArgs) {
case 0:
return ""
case 1:
return fmt.Sprintf("%v", msgAndArgs[0])
default:
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
}
}
// WithCustomMessage accepts one or two messages and formats them appropriately
func WithCustomMessage(source string, msgAndArgs ...interface{}) string {
custom := Message(msgAndArgs...)
switch {
case custom == "":
return source
case source == "":
return custom
}
return fmt.Sprintf("%s: %s", source, custom)
}

View File

@@ -0,0 +1,53 @@
package source
import (
"go/ast"
"go/token"
"github.com/pkg/errors"
)
func scanToDeferLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node {
var matchedNode ast.Node
ast.Inspect(node, func(node ast.Node) bool {
switch {
case node == nil || matchedNode != nil:
return false
case fileset.Position(node.End()).Line == lineNum:
if funcLit, ok := node.(*ast.FuncLit); ok {
matchedNode = funcLit
return false
}
}
return true
})
debug("defer line node: %s", debugFormatNode{matchedNode})
return matchedNode
}
func guessDefer(node ast.Node) (ast.Node, error) {
defers := collectDefers(node)
switch len(defers) {
case 0:
return nil, errors.New("failed to expression in defer")
case 1:
return defers[0].Call, nil
default:
return nil, errors.Errorf(
"ambiguous call expression: multiple (%d) defers in call block",
len(defers))
}
}
func collectDefers(node ast.Node) []*ast.DeferStmt {
var defers []*ast.DeferStmt
ast.Inspect(node, func(node ast.Node) bool {
if d, ok := node.(*ast.DeferStmt); ok {
defers = append(defers, d)
debug("defer: %s", debugFormatNode{d})
return false
}
return true
})
return defers
}

View File

@@ -0,0 +1,166 @@
package source // import "gotest.tools/internal/source"
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"os"
"runtime"
"strconv"
"strings"
"github.com/pkg/errors"
)
const baseStackIndex = 1
// FormattedCallExprArg returns the argument from an ast.CallExpr at the
// index in the call stack. The argument is formatted using FormatNode.
func FormattedCallExprArg(stackIndex int, argPos int) (string, error) {
args, err := CallExprArgs(stackIndex + 1)
if err != nil {
return "", err
}
if argPos >= len(args) {
return "", errors.New("failed to find expression")
}
return FormatNode(args[argPos])
}
// CallExprArgs returns the ast.Expr slice for the args of an ast.CallExpr at
// the index in the call stack.
func CallExprArgs(stackIndex int) ([]ast.Expr, error) {
_, filename, lineNum, ok := runtime.Caller(baseStackIndex + stackIndex)
if !ok {
return nil, errors.New("failed to get call stack")
}
debug("call stack position: %s:%d", filename, lineNum)
node, err := getNodeAtLine(filename, lineNum)
if err != nil {
return nil, err
}
debug("found node: %s", debugFormatNode{node})
return getCallExprArgs(node)
}
func getNodeAtLine(filename string, lineNum int) (ast.Node, error) {
fileset := token.NewFileSet()
astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse source file: %s", filename)
}
if node := scanToLine(fileset, astFile, lineNum); node != nil {
return node, nil
}
if node := scanToDeferLine(fileset, astFile, lineNum); node != nil {
node, err := guessDefer(node)
if err != nil || node != nil {
return node, err
}
}
return nil, errors.Errorf(
"failed to find an expression on line %d in %s", lineNum, filename)
}
func scanToLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node {
var matchedNode ast.Node
ast.Inspect(node, func(node ast.Node) bool {
switch {
case node == nil || matchedNode != nil:
return false
case nodePosition(fileset, node).Line == lineNum:
matchedNode = node
return false
}
return true
})
return matchedNode
}
// In golang 1.9 the line number changed from being the line where the statement
// ended to the line where the statement began.
func nodePosition(fileset *token.FileSet, node ast.Node) token.Position {
if goVersionBefore19 {
return fileset.Position(node.End())
}
return fileset.Position(node.Pos())
}
var goVersionBefore19 = func() bool {
version := runtime.Version()
// not a release version
if !strings.HasPrefix(version, "go") {
return false
}
version = strings.TrimPrefix(version, "go")
parts := strings.Split(version, ".")
if len(parts) < 2 {
return false
}
minor, err := strconv.ParseInt(parts[1], 10, 32)
return err == nil && parts[0] == "1" && minor < 9
}()
func getCallExprArgs(node ast.Node) ([]ast.Expr, error) {
visitor := &callExprVisitor{}
ast.Walk(visitor, node)
if visitor.expr == nil {
return nil, errors.New("failed to find call expression")
}
debug("callExpr: %s", debugFormatNode{visitor.expr})
return visitor.expr.Args, nil
}
type callExprVisitor struct {
expr *ast.CallExpr
}
func (v *callExprVisitor) Visit(node ast.Node) ast.Visitor {
if v.expr != nil || node == nil {
return nil
}
debug("visit: %s", debugFormatNode{node})
switch typed := node.(type) {
case *ast.CallExpr:
v.expr = typed
return nil
case *ast.DeferStmt:
ast.Walk(v, typed.Call.Fun)
return nil
}
return v
}
// FormatNode using go/format.Node and return the result as a string
func FormatNode(node ast.Node) (string, error) {
buf := new(bytes.Buffer)
err := format.Node(buf, token.NewFileSet(), node)
return buf.String(), err
}
var debugEnabled = os.Getenv("GOTESTTOOLS_DEBUG") != ""
func debug(format string, args ...interface{}) {
if debugEnabled {
fmt.Fprintf(os.Stderr, "DEBUG: "+format+"\n", args...)
}
}
type debugFormatNode struct {
ast.Node
}
func (n debugFormatNode) String() string {
out, err := FormatNode(n.Node)
if err != nil {
return fmt.Sprintf("failed to format %s: %s", n.Node, err)
}
return fmt.Sprintf("(%T) %s", n.Node, out)
}

32
vendor/modules.txt vendored
View File

@@ -73,11 +73,11 @@ github.com/beorn7/perks/quantile
github.com/blang/semver github.com/blang/semver
# github.com/cespare/xxhash v1.1.0 => github.com/cespare/xxhash v1.1.0 # github.com/cespare/xxhash v1.1.0 => github.com/cespare/xxhash v1.1.0
github.com/cespare/xxhash github.com/cespare/xxhash
# github.com/cespare/xxhash/v2 v2.1.1 # github.com/cespare/xxhash/v2 v2.1.1 => github.com/cespare/xxhash/v2 v2.1.1
github.com/cespare/xxhash/v2 github.com/cespare/xxhash/v2
# github.com/container-storage-interface/spec v1.2.0 => github.com/container-storage-interface/spec v1.2.0 # github.com/container-storage-interface/spec v1.2.0 => github.com/container-storage-interface/spec v1.2.0
github.com/container-storage-interface/spec/lib/go/csi github.com/container-storage-interface/spec/lib/go/csi
# github.com/containernetworking/cni v0.8.0 # github.com/containernetworking/cni v0.8.0 => github.com/containernetworking/cni v0.8.0
github.com/containernetworking/cni/pkg/types github.com/containernetworking/cni/pkg/types
github.com/containernetworking/cni/pkg/types/020 github.com/containernetworking/cni/pkg/types/020
github.com/containernetworking/cni/pkg/types/current github.com/containernetworking/cni/pkg/types/current
@@ -144,9 +144,9 @@ github.com/elastic/go-elasticsearch/v7/estransport
github.com/elastic/go-elasticsearch/v7/internal/version github.com/elastic/go-elasticsearch/v7/internal/version
# github.com/emicklei/go-restful v2.14.3+incompatible => github.com/emicklei/go-restful v2.14.3+incompatible # github.com/emicklei/go-restful v2.14.3+incompatible => github.com/emicklei/go-restful v2.14.3+incompatible
github.com/emicklei/go-restful github.com/emicklei/go-restful
github.com/emicklei/go-restful/log
# github.com/emicklei/go-restful-openapi v1.4.1 => github.com/emicklei/go-restful-openapi v1.4.1 # github.com/emicklei/go-restful-openapi v1.4.1 => github.com/emicklei/go-restful-openapi v1.4.1
github.com/emicklei/go-restful-openapi github.com/emicklei/go-restful-openapi
github.com/emicklei/go-restful/log
# github.com/emirpasic/gods v1.12.0 => github.com/emirpasic/gods v1.12.0 # github.com/emirpasic/gods v1.12.0 => github.com/emirpasic/gods v1.12.0
github.com/emirpasic/gods/containers github.com/emirpasic/gods/containers
github.com/emirpasic/gods/lists github.com/emirpasic/gods/lists
@@ -245,7 +245,7 @@ github.com/golang/glog
github.com/golang/groupcache/lru github.com/golang/groupcache/lru
# github.com/golang/mock v1.2.0 => github.com/golang/mock v1.2.0 # github.com/golang/mock v1.2.0 => github.com/golang/mock v1.2.0
github.com/golang/mock/gomock github.com/golang/mock/gomock
# github.com/golang/protobuf v1.4.3 => github.com/golang/protobuf v1.3.2 # github.com/golang/protobuf v1.4.0 => github.com/golang/protobuf v1.3.2
github.com/golang/protobuf/descriptor github.com/golang/protobuf/descriptor
github.com/golang/protobuf/jsonpb github.com/golang/protobuf/jsonpb
github.com/golang/protobuf/proto github.com/golang/protobuf/proto
@@ -318,11 +318,11 @@ github.com/inconshreveable/mousetrap
github.com/jbenet/go-context/io github.com/jbenet/go-context/io
# github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af => github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af # github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af => github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
github.com/jmespath/go-jmespath github.com/jmespath/go-jmespath
# github.com/json-iterator/go v1.1.10 => github.com/json-iterator/go v1.1.8 # github.com/json-iterator/go v1.1.9 => github.com/json-iterator/go v1.1.8
github.com/json-iterator/go github.com/json-iterator/go
# github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e => github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e # github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e => github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e
github.com/kevinburke/ssh_config github.com/kevinburke/ssh_config
# github.com/kiali/kiali v1.26.0 => github.com/kubesphere/kiali v0.15.1-0.20201110082537-0c2b977257d4 # github.com/kiali/kiali v0.15.1-0.20201110082537-0c2b977257d4 => github.com/kubesphere/kiali v0.15.1-0.20201110082537-0c2b977257d4
github.com/kiali/kiali/business github.com/kiali/kiali/business
github.com/kiali/kiali/business/checkers github.com/kiali/kiali/business/checkers
github.com/kiali/kiali/business/checkers/destinationrules github.com/kiali/kiali/business/checkers/destinationrules
@@ -498,9 +498,9 @@ github.com/projectcalico/libcalico-go/lib/selector
github.com/projectcalico/libcalico-go/lib/selector/parser github.com/projectcalico/libcalico-go/lib/selector/parser
github.com/projectcalico/libcalico-go/lib/selector/tokenizer github.com/projectcalico/libcalico-go/lib/selector/tokenizer
github.com/projectcalico/libcalico-go/lib/set github.com/projectcalico/libcalico-go/lib/set
# github.com/prometheus-community/prom-label-proxy v0.2.0 # github.com/prometheus-community/prom-label-proxy v0.2.0 => github.com/prometheus-community/prom-label-proxy v0.2.0
github.com/prometheus-community/prom-label-proxy/injectproxy github.com/prometheus-community/prom-label-proxy/injectproxy
# github.com/prometheus/alertmanager v0.20.0 # github.com/prometheus/alertmanager v0.20.0 => github.com/prometheus/alertmanager v0.20.0
github.com/prometheus/alertmanager/api/v2/client github.com/prometheus/alertmanager/api/v2/client
github.com/prometheus/alertmanager/api/v2/client/alert github.com/prometheus/alertmanager/api/v2/client/alert
github.com/prometheus/alertmanager/api/v2/client/alertgroup github.com/prometheus/alertmanager/api/v2/client/alertgroup
@@ -509,7 +509,7 @@ github.com/prometheus/alertmanager/api/v2/client/receiver
github.com/prometheus/alertmanager/api/v2/client/silence github.com/prometheus/alertmanager/api/v2/client/silence
github.com/prometheus/alertmanager/api/v2/models github.com/prometheus/alertmanager/api/v2/models
github.com/prometheus/alertmanager/pkg/labels github.com/prometheus/alertmanager/pkg/labels
# github.com/prometheus/client_golang v1.8.0 => github.com/prometheus/client_golang v1.5.1 # github.com/prometheus/client_golang v1.5.1 => github.com/prometheus/client_golang v1.5.1
github.com/prometheus/client_golang/api github.com/prometheus/client_golang/api
github.com/prometheus/client_golang/api/prometheus/v1 github.com/prometheus/client_golang/api/prometheus/v1
github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus
@@ -517,7 +517,7 @@ github.com/prometheus/client_golang/prometheus/internal
github.com/prometheus/client_golang/prometheus/promhttp github.com/prometheus/client_golang/prometheus/promhttp
# github.com/prometheus/client_model v0.2.0 => github.com/prometheus/client_model v0.2.0 # github.com/prometheus/client_model v0.2.0 => github.com/prometheus/client_model v0.2.0
github.com/prometheus/client_model/go github.com/prometheus/client_model/go
# github.com/prometheus/common v0.14.0 => github.com/prometheus/common v0.9.1 # github.com/prometheus/common v0.9.1 => github.com/prometheus/common v0.9.1
github.com/prometheus/common/expfmt github.com/prometheus/common/expfmt
github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg
github.com/prometheus/common/log github.com/prometheus/common/log
@@ -526,7 +526,7 @@ github.com/prometheus/common/model
github.com/prometheus/procfs github.com/prometheus/procfs
github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/fs
github.com/prometheus/procfs/internal/util github.com/prometheus/procfs/internal/util
# github.com/prometheus/prometheus v2.5.0+incompatible => github.com/prometheus/prometheus v1.8.2-0.20200507164740-ecee9c8abfd1 # github.com/prometheus/prometheus v1.8.2-0.20200507164740-ecee9c8abfd1 => github.com/prometheus/prometheus v1.8.2-0.20200507164740-ecee9c8abfd1
github.com/prometheus/prometheus/pkg/labels github.com/prometheus/prometheus/pkg/labels
github.com/prometheus/prometheus/pkg/value github.com/prometheus/prometheus/pkg/value
github.com/prometheus/prometheus/promql/parser github.com/prometheus/prometheus/promql/parser
@@ -632,7 +632,7 @@ golang.org/x/crypto/ssh
golang.org/x/crypto/ssh/agent golang.org/x/crypto/ssh/agent
golang.org/x/crypto/ssh/knownhosts golang.org/x/crypto/ssh/knownhosts
golang.org/x/crypto/ssh/terminal golang.org/x/crypto/ssh/terminal
# golang.org/x/net v0.0.0-20200625001655-4c5254603344 => golang.org/x/net v0.0.0-20190620200207-3b0461eec859 # golang.org/x/net v0.0.0-20200421231249-e086a090c8fd => golang.org/x/net v0.0.0-20190620200207-3b0461eec859
golang.org/x/net/context golang.org/x/net/context
golang.org/x/net/context/ctxhttp golang.org/x/net/context/ctxhttp
golang.org/x/net/html golang.org/x/net/html
@@ -807,10 +807,16 @@ gopkg.in/src-d/go-git.v4/utils/merkletrie/noder
gopkg.in/tomb.v1 gopkg.in/tomb.v1
# gopkg.in/warnings.v0 v0.1.2 => gopkg.in/warnings.v0 v0.1.2 # gopkg.in/warnings.v0 v0.1.2 => gopkg.in/warnings.v0 v0.1.2
gopkg.in/warnings.v0 gopkg.in/warnings.v0
# gopkg.in/yaml.v2 v2.3.0 => gopkg.in/yaml.v2 v2.2.8 # gopkg.in/yaml.v2 v2.2.8 => gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v2 gopkg.in/yaml.v2
# gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c => gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 # gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c => gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966
gopkg.in/yaml.v3 gopkg.in/yaml.v3
# gotest.tools v2.2.0+incompatible => gotest.tools v2.2.0+incompatible
gotest.tools/assert
gotest.tools/assert/cmp
gotest.tools/internal/difflib
gotest.tools/internal/format
gotest.tools/internal/source
# istio.io/api v0.0.0-20191111210003-35e06ef8d838 => istio.io/api v0.0.0-20191111210003-35e06ef8d838 # istio.io/api v0.0.0-20191111210003-35e06ef8d838 => istio.io/api v0.0.0-20191111210003-35e06ef8d838
istio.io/api/authentication/v1alpha1 istio.io/api/authentication/v1alpha1
istio.io/api/mixer/v1 istio.io/api/mixer/v1