Merge branch 'dev' into devops-refactor
This commit is contained in:
59
.github/workflows/build.yml
vendored
Normal file
59
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
name: Go
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'dev'
|
||||||
|
tags:
|
||||||
|
- 'release-*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'dev'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GO111MODULE: on
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Set up Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Check pr is properly formatted
|
||||||
|
run: diff -u <(echo -n) <(gofmt -d ./pkg ./cmd ./tools ./test)
|
||||||
|
|
||||||
|
- name: Downloading go dependencies
|
||||||
|
run: go mod vendor
|
||||||
|
|
||||||
|
- name: Install kubebuilder
|
||||||
|
run: bash hack/install_kubebuilder.sh
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: make all
|
||||||
|
|
||||||
|
- name: Uploading code coverage
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
run: bash <(curl -s https://codecov.io/bash)
|
||||||
|
|
||||||
|
- name: Get branch name
|
||||||
|
id: extract_branch
|
||||||
|
shell: bash
|
||||||
|
run: echo "##[set-output name=branch;]$(ehco ${GITHUB_REF#refs/heads/})"
|
||||||
|
|
||||||
|
- name: Build and push docker images
|
||||||
|
env:
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
run: bash hack/docker_build.sh ${{ steps.extract_branch.outputs.branch }}
|
||||||
6
Makefile
6
Makefile
@@ -39,16 +39,12 @@ define ALL_HELP_INFO
|
|||||||
# debugging tools like delve.
|
# debugging tools like delve.
|
||||||
endef
|
endef
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: test hypersphere ks-apiserver ks-apigateway controller-manager
|
all: test hypersphere ks-apiserver controller-manager
|
||||||
|
|
||||||
# Build ks-apiserver binary
|
# Build ks-apiserver binary
|
||||||
ks-apiserver: fmt vet
|
ks-apiserver: fmt vet
|
||||||
hack/gobuild.sh cmd/ks-apiserver
|
hack/gobuild.sh cmd/ks-apiserver
|
||||||
|
|
||||||
# Build ks-apigateway binary
|
|
||||||
ks-apigateway: fmt vet
|
|
||||||
hack/gobuild.sh cmd/ks-apigateway
|
|
||||||
|
|
||||||
# Build controller-manager binary
|
# Build controller-manager binary
|
||||||
controller-manager: fmt vet
|
controller-manager: fmt vet
|
||||||
hack/gobuild.sh cmd/controller-manager
|
hack/gobuild.sh cmd/controller-manager
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
# Copyright 2018 The KubeSphere Authors. All rights reserved.
|
|
||||||
# Use of this source code is governed by a Apache license
|
|
||||||
# that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
# Copyright 2018 The KubeSphere Authors. All rights reserved.
|
|
||||||
# Use of this source code is governed by a Apache license
|
|
||||||
# that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
FROM golang:1.12 as ks-apigateway-builder
|
|
||||||
|
|
||||||
COPY / /go/src/kubesphere.io/kubesphere
|
|
||||||
WORKDIR /go/src/kubesphere.io/kubesphere
|
|
||||||
RUN CGO_ENABLED=0 GO111MODULE=on GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor go build -i -ldflags '-w -s' -o ks-apigateway cmd/ks-apigateway/apiserver.go && \
|
|
||||||
go run tools/cmd/doc-gen/main.go --output=install/swagger-ui/api.json
|
|
||||||
|
|
||||||
FROM alpine:3.9
|
|
||||||
RUN apk add --update ca-certificates && update-ca-certificates
|
|
||||||
COPY --from=ks-apigateway-builder /go/src/kubesphere.io/kubesphere/ks-apigateway /usr/local/bin/
|
|
||||||
COPY --from=ks-apigateway-builder /go/src/kubesphere.io/kubesphere/install/swagger-ui /var/static/swagger-ui
|
|
||||||
CMD ["sh"]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Copyright 2018 The KubeSphere Authors. All rights reserved.
|
|
||||||
# Use of this source code is governed by a Apache license
|
|
||||||
# that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
# Copyright 2018 The KubeSphere Authors. All rights reserved.
|
|
||||||
# Use of this source code is governed by a Apache license
|
|
||||||
# that can be found in the LICENSE file.
|
|
||||||
FROM golang:1.12 as ks-iam-builder
|
|
||||||
|
|
||||||
COPY / /go/src/kubesphere.io/kubesphere
|
|
||||||
|
|
||||||
WORKDIR /go/src/kubesphere.io/kubesphere
|
|
||||||
RUN CGO_ENABLED=0 GO111MODULE=on GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor go build -i -ldflags '-w -s' -o ks-iam cmd/ks-iam/apiserver.go
|
|
||||||
|
|
||||||
FROM alpine:3.9
|
|
||||||
RUN apk add --update ca-certificates && update-ca-certificates
|
|
||||||
COPY --from=ks-iam-builder /go/src/kubesphere.io/kubesphere/ks-iam /usr/local/bin/
|
|
||||||
CMD ["sh"]
|
|
||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
controllermanager "kubesphere.io/kubesphere/cmd/controller-manager/app"
|
controllermanager "kubesphere.io/kubesphere/cmd/controller-manager/app"
|
||||||
ksapigateway "kubesphere.io/kubesphere/cmd/ks-apigateway/app"
|
|
||||||
ksapiserver "kubesphere.io/kubesphere/cmd/ks-apiserver/app"
|
ksapiserver "kubesphere.io/kubesphere/cmd/ks-apiserver/app"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@@ -45,12 +44,10 @@ func commandFor(basename string, defaultCommand *cobra.Command, commands []func(
|
|||||||
func NewHyperSphereCommand() (*cobra.Command, []func() *cobra.Command) {
|
func NewHyperSphereCommand() (*cobra.Command, []func() *cobra.Command) {
|
||||||
apiserver := func() *cobra.Command { return ksapiserver.NewAPIServerCommand() }
|
apiserver := func() *cobra.Command { return ksapiserver.NewAPIServerCommand() }
|
||||||
controllermanager := func() *cobra.Command { return controllermanager.NewControllerManagerCommand() }
|
controllermanager := func() *cobra.Command { return controllermanager.NewControllerManagerCommand() }
|
||||||
apigateway := func() *cobra.Command { return ksapigateway.NewAPIGatewayCommand() }
|
|
||||||
|
|
||||||
commandFns := []func() *cobra.Command{
|
commandFns := []func() *cobra.Command{
|
||||||
apiserver,
|
apiserver,
|
||||||
controllermanager,
|
controllermanager,
|
||||||
apigateway,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright 2019 The KubeSphere Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"kubesphere.io/kubesphere/cmd/ks-apigateway/app"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
cmd := app.NewAPIGatewayCommand()
|
|
||||||
|
|
||||||
if err := cmd.Execute(); err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"github.com/mholt/caddy/caddy/caddymain"
|
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"kubesphere.io/kubesphere/pkg/utils/signals"
|
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/apigateway"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewAPIGatewayCommand() *cobra.Command {
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "ks-apigateway",
|
|
||||||
Long: `The KubeSphere API Gateway, which is responsible
|
|
||||||
for proxy request to the right backend. API Gateway also proxy
|
|
||||||
Kubernetes API Server for KubeSphere authorization purpose.
|
|
||||||
`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
apigateway.RegisterPlugins()
|
|
||||||
|
|
||||||
return Run(signals.SetupSignalHandler())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().AddGoFlagSet(flag.CommandLine)
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func Run(stopCh <-chan struct{}) error {
|
|
||||||
httpserver.RegisterDevDirective("authenticate", "jwt")
|
|
||||||
httpserver.RegisterDevDirective("authentication", "jwt")
|
|
||||||
httpserver.RegisterDevDirective("swagger", "jwt")
|
|
||||||
caddymain.Run()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
cliflag "k8s.io/component-base/cli/flag"
|
cliflag "k8s.io/component-base/cli/flag"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver"
|
"kubesphere.io/kubesphere/pkg/apiserver"
|
||||||
"kubesphere.io/kubesphere/pkg/informers"
|
"kubesphere.io/kubesphere/pkg/informers"
|
||||||
genericoptions "kubesphere.io/kubesphere/pkg/server/options"
|
genericoptions "kubesphere.io/kubesphere/pkg/server/options"
|
||||||
@@ -40,7 +40,7 @@ type ServerRunOptions struct {
|
|||||||
LoggingOptions *esclient.Options
|
LoggingOptions *esclient.Options
|
||||||
LdapOptions *ldap.Options
|
LdapOptions *ldap.Options
|
||||||
CacheOptions *cache.Options
|
CacheOptions *cache.Options
|
||||||
AuthenticateOptions *iam.AuthenticationOptions
|
AuthenticateOptions *auth.AuthenticationOptions
|
||||||
|
|
||||||
//
|
//
|
||||||
DebugMode bool
|
DebugMode bool
|
||||||
@@ -61,7 +61,7 @@ func NewServerRunOptions() *ServerRunOptions {
|
|||||||
LoggingOptions: esclient.NewElasticSearchOptions(),
|
LoggingOptions: esclient.NewElasticSearchOptions(),
|
||||||
LdapOptions: ldap.NewOptions(),
|
LdapOptions: ldap.NewOptions(),
|
||||||
CacheOptions: cache.NewRedisOptions(),
|
CacheOptions: cache.NewRedisOptions(),
|
||||||
AuthenticateOptions: iam.NewAuthenticateOptions(),
|
AuthenticateOptions: auth.NewAuthenticateOptions(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return &s
|
return &s
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ func NewAPIServerCommand() *cobra.Command {
|
|||||||
S3Options: conf.S3Options,
|
S3Options: conf.S3Options,
|
||||||
OpenPitrixOptions: conf.OpenPitrixOptions,
|
OpenPitrixOptions: conf.OpenPitrixOptions,
|
||||||
LoggingOptions: conf.LoggingOptions,
|
LoggingOptions: conf.LoggingOptions,
|
||||||
|
LdapOptions: conf.LdapOptions,
|
||||||
|
CacheOptions: conf.RedisOptions,
|
||||||
AuthenticateOptions: conf.AuthenticateOptions,
|
AuthenticateOptions: conf.AuthenticateOptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
go.mod
11
go.mod
@@ -55,7 +55,6 @@ require (
|
|||||||
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
||||||
github.com/kiali/kiali v0.15.1-0.20191210080139-edbbad1ef779
|
github.com/kiali/kiali v0.15.1-0.20191210080139-edbbad1ef779
|
||||||
github.com/klauspost/cpuid v1.2.1 // indirect
|
github.com/klauspost/cpuid v1.2.1 // indirect
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
|
||||||
github.com/kubernetes-sigs/application v0.0.0-20191210100950-18cc93526ab4
|
github.com/kubernetes-sigs/application v0.0.0-20191210100950-18cc93526ab4
|
||||||
github.com/kubesphere/sonargo v0.0.2
|
github.com/kubesphere/sonargo v0.0.2
|
||||||
github.com/leodido/go-urn v1.1.0 // indirect
|
github.com/leodido/go-urn v1.1.0 // indirect
|
||||||
@@ -69,6 +68,7 @@ require (
|
|||||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
||||||
github.com/onsi/ginkgo v1.8.0
|
github.com/onsi/ginkgo v1.8.0
|
||||||
github.com/onsi/gomega v1.5.0
|
github.com/onsi/gomega v1.5.0
|
||||||
|
github.com/open-policy-agent/opa v0.18.0
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
github.com/opencontainers/go-digest v1.0.0-rc1
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
github.com/openshift/api v3.9.0+incompatible // indirect
|
github.com/openshift/api v3.9.0+incompatible // indirect
|
||||||
@@ -84,7 +84,6 @@ require (
|
|||||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
|
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||||
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 // indirect
|
|
||||||
google.golang.org/grpc v1.23.1
|
google.golang.org/grpc v1.23.1
|
||||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||||
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
|
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
|
||||||
@@ -208,6 +207,7 @@ replace (
|
|||||||
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/flect => github.com/gobuffalo/flect v0.1.5
|
github.com/gobuffalo/flect => github.com/gobuffalo/flect v0.1.5
|
||||||
|
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/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
|
||||||
@@ -277,6 +277,7 @@ replace (
|
|||||||
github.com/marten-seemann/qtls => github.com/marten-seemann/qtls v0.2.3
|
github.com/marten-seemann/qtls => github.com/marten-seemann/qtls v0.2.3
|
||||||
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-sqlite3 => github.com/mattn/go-sqlite3 v1.11.0
|
github.com/mattn/go-sqlite3 => github.com/mattn/go-sqlite3 v1.11.0
|
||||||
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/mholt/caddy => github.com/mholt/caddy v1.0.0
|
github.com/mholt/caddy => github.com/mholt/caddy v1.0.0
|
||||||
@@ -284,6 +285,7 @@ replace (
|
|||||||
github.com/miekg/dns => github.com/miekg/dns v1.1.9
|
github.com/miekg/dns => github.com/miekg/dns v1.1.9
|
||||||
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/mapstructure => github.com/mitchellh/mapstructure v1.1.2
|
github.com/mitchellh/mapstructure => github.com/mitchellh/mapstructure v1.1.2
|
||||||
|
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/morikuni/aec => github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c
|
github.com/morikuni/aec => github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c
|
||||||
@@ -293,8 +295,10 @@ replace (
|
|||||||
github.com/naoina/go-stringutil => github.com/naoina/go-stringutil v0.1.0
|
github.com/naoina/go-stringutil => github.com/naoina/go-stringutil v0.1.0
|
||||||
github.com/naoina/toml => github.com/naoina/toml v0.1.1
|
github.com/naoina/toml => github.com/naoina/toml v0.1.1
|
||||||
github.com/oklog/ulid => github.com/oklog/ulid v1.3.1
|
github.com/oklog/ulid => github.com/oklog/ulid v1.3.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
|
||||||
|
github.com/open-policy-agent/opa => github.com/open-policy-agent/opa v0.18.0
|
||||||
github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.0-rc1
|
github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.0-rc1
|
||||||
github.com/opencontainers/image-spec => github.com/opencontainers/image-spec v1.0.1
|
github.com/opencontainers/image-spec => github.com/opencontainers/image-spec v1.0.1
|
||||||
github.com/openshift/api => github.com/openshift/api v3.9.0+incompatible
|
github.com/openshift/api => github.com/openshift/api v3.9.0+incompatible
|
||||||
@@ -302,6 +306,7 @@ replace (
|
|||||||
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
|
||||||
github.com/peterbourgon/diskv => github.com/peterbourgon/diskv v2.0.1+incompatible
|
github.com/peterbourgon/diskv => github.com/peterbourgon/diskv v2.0.1+incompatible
|
||||||
|
github.com/peterh/liner => github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d
|
||||||
github.com/philhofer/fwd => github.com/philhofer/fwd v1.0.0
|
github.com/philhofer/fwd => github.com/philhofer/fwd v1.0.0
|
||||||
github.com/pkg/errors => github.com/pkg/errors v0.8.1
|
github.com/pkg/errors => github.com/pkg/errors v0.8.1
|
||||||
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
|
||||||
@@ -316,6 +321,7 @@ replace (
|
|||||||
github.com/prometheus/common => github.com/prometheus/common v0.4.0
|
github.com/prometheus/common => github.com/prometheus/common v0.4.0
|
||||||
github.com/prometheus/procfs => github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084
|
github.com/prometheus/procfs => github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084
|
||||||
github.com/prometheus/tsdb => github.com/prometheus/tsdb v0.7.1
|
github.com/prometheus/tsdb => github.com/prometheus/tsdb v0.7.1
|
||||||
|
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/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
|
||||||
@@ -346,6 +352,7 @@ replace (
|
|||||||
github.com/xiang90/probing => github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2
|
github.com/xiang90/probing => github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2
|
||||||
github.com/xlab/treeprint => github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6
|
github.com/xlab/treeprint => github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6
|
||||||
github.com/xordataexchange/crypt => github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77
|
github.com/xordataexchange/crypt => github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77
|
||||||
|
github.com/yashtewari/glob-intersection => github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b
|
||||||
go.etcd.io/bbolt => go.etcd.io/bbolt v1.3.3
|
go.etcd.io/bbolt => go.etcd.io/bbolt v1.3.3
|
||||||
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
|
||||||
|
|||||||
15
go.sum
15
go.sum
@@ -24,6 +24,7 @@ github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiU
|
|||||||
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
|
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
|
||||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
||||||
@@ -59,6 +60,7 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn
|
|||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||||
@@ -176,6 +178,8 @@ 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/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/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/gocraft/dbr v0.0.0-20180507214907-a0fd650918f6 h1:kumyNm8Vr8cbVm/aLQYTbDE3SKCbbn5HEVoDp/Dyyfc=
|
github.com/gocraft/dbr v0.0.0-20180507214907-a0fd650918f6 h1:kumyNm8Vr8cbVm/aLQYTbDE3SKCbbn5HEVoDp/Dyyfc=
|
||||||
github.com/gocraft/dbr v0.0.0-20180507214907-a0fd650918f6/go.mod h1:K/9g3pPouf13kP5K7pdriQEJAy272R9yXuWuDIEWJTM=
|
github.com/gocraft/dbr v0.0.0-20180507214907-a0fd650918f6/go.mod h1:K/9g3pPouf13kP5K7pdriQEJAy272R9yXuWuDIEWJTM=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
@@ -296,6 +300,7 @@ github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx
|
|||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
@@ -310,6 +315,7 @@ 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/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/mna/pigeon v0.0.0-20180808201053-bb0192cfc2ae/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
@@ -326,10 +332,13 @@ github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h
|
|||||||
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
|
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
|
||||||
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
|
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
|
||||||
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=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/open-policy-agent/opa v0.18.0 h1:EC81mO3/517Kq5brJHydqKE5MLzJ+4cdJvUQKxLzHy8=
|
||||||
|
github.com/open-policy-agent/opa v0.18.0/go.mod h1:6pC1cMYDI92i9EY/GoA2m+HcZlcCrh3jbfny5F7JVTA=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||||
@@ -344,6 +353,7 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181
|
|||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
|
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@@ -369,6 +379,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzr
|
|||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
|
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
|
||||||
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||||
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=
|
||||||
@@ -386,6 +398,7 @@ github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
|||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009 h1:3wBL/e/qjpSYaXacpbIV+Bsj/nwQ4UO1llG/av54zzw=
|
github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009 h1:3wBL/e/qjpSYaXacpbIV+Bsj/nwQ4UO1llG/av54zzw=
|
||||||
github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009/go.mod h1:dVvZuWJd174umvm5g8CmZD6S2GWwHKtpK/0ZPHswuNo=
|
github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009/go.mod h1:dVvZuWJd174umvm5g8CmZD6S2GWwHKtpK/0ZPHswuNo=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw=
|
github.com/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw=
|
||||||
github.com/speps/go-hashids v2.0.0+incompatible/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc=
|
github.com/speps/go-hashids v2.0.0+incompatible/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc=
|
||||||
@@ -418,6 +431,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5
|
|||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
|
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY=
|
||||||
|
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co=
|
||||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
GV="network:v1alpha1 servicemesh:v1alpha2 tenant:v1alpha1 devops:v1alpha1 devops:v1alpha3"
|
GV="network:v1alpha1 servicemesh:v1alpha2 tenant:v1alpha1 devops:v1alpha1 devops:v1alpha3 tower:v1alpha1"
|
||||||
|
|
||||||
rm -rf ./pkg/client
|
rm -rf ./pkg/client
|
||||||
./hack/generate_group.sh "client,lister,informer" kubesphere.io/kubesphere/pkg/client kubesphere.io/kubesphere/pkg/apis "$GV" --output-base=./ -h "$PWD/hack/boilerplate.go.txt"
|
./hack/generate_group.sh "client,lister,informer" kubesphere.io/kubesphere/pkg/client kubesphere.io/kubesphere/pkg/apis "$GV" --output-base=./ -h "$PWD/hack/boilerplate.go.txt"
|
||||||
|
|||||||
@@ -1,4 +1,22 @@
|
|||||||
package iam
|
/*
|
||||||
|
*
|
||||||
|
* 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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
48
pkg/api/auth/types.go
Normal file
48
pkg/api/auth/types.go
Normal 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 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request *TokenReview) Validate() error {
|
||||||
|
if request.Spec == nil || request.Spec.Token == "" {
|
||||||
|
return fmt.Errorf("token must not be null")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package token
|
|
||||||
|
|
||||||
// Issuer issues token to user, tokens are required to perform mutating requests to resources
|
|
||||||
type Issuer interface {
|
|
||||||
// IssueTo issues a token a User, return error if issuing process failed
|
|
||||||
IssueTo(User) (string, error)
|
|
||||||
|
|
||||||
// Verify verifies a token, and return a User if it's a valid token, otherwise return error
|
|
||||||
Verify(string) (User, error)
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package token
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
|
||||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
|
||||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultIssuerName = "kubesphere"
|
|
||||||
|
|
||||||
var errInvalidToken = errors.New("invalid token")
|
|
||||||
|
|
||||||
type claims struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
UID string `json:"uid"`
|
|
||||||
// Currently, we are not using any field in jwt.StandardClaims
|
|
||||||
jwt.StandardClaims
|
|
||||||
}
|
|
||||||
|
|
||||||
type jwtTokenIssuer struct {
|
|
||||||
name string
|
|
||||||
secret []byte
|
|
||||||
keyFunc jwt.Keyfunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
|
|
||||||
if len(tokenString) == 0 {
|
|
||||||
return nil, errInvalidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
clm := &claims{}
|
|
||||||
|
|
||||||
_, err := jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &iam.User{Username: clm.Username, Email: clm.UID}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *jwtTokenIssuer) IssueTo(user User) (string, error) {
|
|
||||||
clm := &claims{
|
|
||||||
Username: user.Name(),
|
|
||||||
UID: user.UID(),
|
|
||||||
StandardClaims: jwt.StandardClaims{
|
|
||||||
IssuedAt: time.Now().Unix(),
|
|
||||||
Issuer: s.name,
|
|
||||||
NotBefore: time.Now().Unix(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, clm)
|
|
||||||
tokenString, err := token.SignedString(s.secret)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenString, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJwtTokenIssuer(issuerName string, secret []byte) Issuer {
|
|
||||||
return &jwtTokenIssuer{
|
|
||||||
name: issuerName,
|
|
||||||
secret: secret,
|
|
||||||
keyFunc: func(token *jwt.Token) (i interface{}, err error) {
|
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
|
|
||||||
return secret, nil
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package token
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestJwtTokenIssuer(t *testing.T) {
|
|
||||||
issuer := NewJwtTokenIssuer(DefaultIssuerName, []byte("kubesphere"))
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
description string
|
|
||||||
name string
|
|
||||||
email string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "admin",
|
|
||||||
email: "admin@kubesphere.io",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bar",
|
|
||||||
email: "bar@kubesphere.io",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
user := &iam.User{
|
|
||||||
Username: testCase.name,
|
|
||||||
Email: testCase.email,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(testCase.description, func(t *testing.T) {
|
|
||||||
token, err := issuer.IssueTo(user)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := issuer.Verify(token)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if diff := cmp.Diff(user, got); len(diff) != 0 {
|
|
||||||
t.Errorf("%T differ (-got, +expected), %s", user, diff)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package token
|
|
||||||
|
|
||||||
type User interface {
|
|
||||||
// Name
|
|
||||||
Name() string
|
|
||||||
|
|
||||||
UID() string
|
|
||||||
}
|
|
||||||
@@ -6,37 +6,30 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Username string `json:"username"`
|
Name string `json:"username"`
|
||||||
|
UID string `json:"uid"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Lang string `json:"lang,omitempty"`
|
Lang string `json:"lang,omitempty"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
CreateTime time.Time `json:"create_time"`
|
CreateTime time.Time `json:"createTime"`
|
||||||
Groups []string `json:"groups,omitempty"`
|
Groups []string `json:"groups,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser() *User {
|
func (u *User) GetName() string {
|
||||||
return &User{
|
return u.Name
|
||||||
Username: "",
|
|
||||||
Email: "",
|
|
||||||
Lang: "",
|
|
||||||
Description: "",
|
|
||||||
CreateTime: time.Time{},
|
|
||||||
Groups: nil,
|
|
||||||
Password: "",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) Name() string {
|
func (u *User) GetUID() string {
|
||||||
return u.Username
|
return u.UID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) UID() string {
|
func (u *User) GetEmail() string {
|
||||||
return u.Email
|
return u.Email
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) Validate() error {
|
func (u *User) Validate() error {
|
||||||
if u.Username == "" {
|
if u.Name == "" {
|
||||||
return errors.New("username can not be empty")
|
return errors.New("username can not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,110 +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 v1alpha2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
|
||||||
"net/mail"
|
|
||||||
)
|
|
||||||
|
|
||||||
const minPasswordLength = 6
|
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserDetail struct {
|
|
||||||
*iam.User
|
|
||||||
ClusterRole string `json:"cluster_role"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateUserRequest struct {
|
|
||||||
*UserDetail
|
|
||||||
}
|
|
||||||
|
|
||||||
func (request *CreateUserRequest) Validate() error {
|
|
||||||
if request.Username == "" {
|
|
||||||
return fmt.Errorf("username must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses a single RFC 5322 address, e.g. "Barry Gibbs <bg@example.com>"
|
|
||||||
if _, err := mail.ParseAddress(request.Email); err != nil {
|
|
||||||
return fmt.Errorf("invalid email: %s", request.Email)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(request.Password) < minPasswordLength {
|
|
||||||
return fmt.Errorf("password must be at least %d characters long", minPasswordLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModifyUserRequest struct {
|
|
||||||
*UserDetail
|
|
||||||
CurrentPassword string `json:"current_password,omitempty" description:"this is necessary if you need to change your password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (request *TokenReview) Validate() error {
|
|
||||||
if request.Spec == nil || request.Spec.Token == "" {
|
|
||||||
return fmt.Errorf("token must not be null")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (request ModifyUserRequest) Validate() error {
|
|
||||||
|
|
||||||
// Parses a single RFC 5322 address, e.g. "Barry Gibbs <bg@example.com>"
|
|
||||||
if _, err := mail.ParseAddress(request.Email); err != nil {
|
|
||||||
return fmt.Errorf("invalid email: %s", request.Email)
|
|
||||||
}
|
|
||||||
|
|
||||||
if request.Password != "" {
|
|
||||||
if len(request.Password) < minPasswordLength {
|
|
||||||
return fmt.Errorf("password must be at least %d characters long", minPasswordLength)
|
|
||||||
}
|
|
||||||
if len(request.CurrentPassword) < minPasswordLength {
|
|
||||||
return fmt.Errorf("password must be at least %d characters long", minPasswordLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListUserResponse struct {
|
|
||||||
Items []*UserDetail `json:"items"`
|
|
||||||
TotalCount int `json:"total_count"`
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright 2019 The KubeSphere Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package authenticate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/klog"
|
|
||||||
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/internal"
|
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Auth struct {
|
|
||||||
Rule *Rule
|
|
||||||
Next httpserver.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule struct {
|
|
||||||
Secret []byte
|
|
||||||
Path string
|
|
||||||
RedisOptions *cache.Options
|
|
||||||
TokenIdleTimeout time.Duration
|
|
||||||
RedisClient cache.Interface
|
|
||||||
ExclusionRules []internal.ExclusionRule
|
|
||||||
}
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
UID string `json:"uid"`
|
|
||||||
Groups *[]string `json:"groups,omitempty"`
|
|
||||||
Extra *map[string]interface{} `json:"extra,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var requestInfoFactory = request.RequestInfoFactory{
|
|
||||||
APIPrefixes: sets.NewString("api", "apis", "kapis", "kapi"),
|
|
||||||
GrouplessAPIPrefixes: sets.NewString("api")}
|
|
||||||
|
|
||||||
func (h Auth) ServeHTTP(resp http.ResponseWriter, req *http.Request) (int, error) {
|
|
||||||
for _, rule := range h.Rule.ExclusionRules {
|
|
||||||
if httpserver.Path(req.URL.Path).Matches(rule.Path) && (rule.Method == internal.AllMethod || req.Method == rule.Method) {
|
|
||||||
return h.Next.ServeHTTP(resp, req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if httpserver.Path(req.URL.Path).Matches(h.Rule.Path) {
|
|
||||||
|
|
||||||
uToken, err := h.ExtractToken(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return h.HandleUnauthorized(resp, err), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := h.Validate(uToken)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return h.HandleUnauthorized(resp, err), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err = h.InjectContext(req, token)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return h.HandleUnauthorized(resp, err), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.Next.ServeHTTP(resp, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Auth) InjectContext(req *http.Request, token *jwt.Token) (*http.Request, error) {
|
|
||||||
|
|
||||||
payload, ok := token.Claims.(jwt.MapClaims)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("invalid payload")
|
|
||||||
}
|
|
||||||
|
|
||||||
for header := range req.Header {
|
|
||||||
if strings.HasPrefix(header, "X-Token-") {
|
|
||||||
req.Header.Del(header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
usr := &user.DefaultInfo{}
|
|
||||||
|
|
||||||
username, ok := payload["username"].(string)
|
|
||||||
|
|
||||||
if ok && username != "" {
|
|
||||||
req.Header.Set("X-Token-Username", username)
|
|
||||||
usr.Name = username
|
|
||||||
}
|
|
||||||
|
|
||||||
uid := payload["uid"]
|
|
||||||
|
|
||||||
if uid != nil {
|
|
||||||
switch uid.(type) {
|
|
||||||
case int:
|
|
||||||
req.Header.Set("X-Token-UID", strconv.Itoa(uid.(int)))
|
|
||||||
usr.UID = strconv.Itoa(uid.(int))
|
|
||||||
break
|
|
||||||
case string:
|
|
||||||
req.Header.Set("X-Token-UID", uid.(string))
|
|
||||||
usr.UID = uid.(string)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
groups, ok := payload["groups"].([]string)
|
|
||||||
if ok && len(groups) > 0 {
|
|
||||||
req.Header.Set("X-Token-Groups", strings.Join(groups, ","))
|
|
||||||
usr.Groups = groups
|
|
||||||
}
|
|
||||||
|
|
||||||
// hard code, support jenkins auth plugin
|
|
||||||
if httpserver.Path(req.URL.Path).Matches("/kapis/jenkins.kubesphere.io") ||
|
|
||||||
httpserver.Path(req.URL.Path).Matches("job") ||
|
|
||||||
httpserver.Path(req.URL.Path).Matches("/kapis/devops.kubesphere.io/v1alpha2") {
|
|
||||||
req.SetBasicAuth(username, token.Raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
context := request.WithUser(req.Context(), usr)
|
|
||||||
|
|
||||||
requestInfo, err := requestInfoFactory.NewRequestInfo(req)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
context = request.WithRequestInfo(context, requestInfo)
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req = req.WithContext(context)
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Auth) Validate(uToken string) (*jwt.Token, error) {
|
|
||||||
|
|
||||||
if len(uToken) == 0 {
|
|
||||||
return nil, fmt.Errorf("token length is zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := jwt.Parse(uToken, h.ProvideKey)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, ok := token.Claims.(jwt.MapClaims)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
err := fmt.Errorf("invalid payload")
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
username, ok := payload["username"].(string)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
err := fmt.Errorf("invalid payload")
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok = payload["exp"]; ok {
|
|
||||||
// allow static token has expiration time
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenKey := fmt.Sprintf("kubesphere:users:%s:token:%s", username, uToken)
|
|
||||||
|
|
||||||
exist, err := h.Rule.RedisClient.Exists(tokenKey)
|
|
||||||
if err != nil {
|
|
||||||
klog.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if exist {
|
|
||||||
// reset expiration time if token exist
|
|
||||||
h.Rule.RedisClient.Expire(tokenKey, h.Rule.TokenIdleTimeout)
|
|
||||||
return token, nil
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("illegal token")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Auth) HandleUnauthorized(w http.ResponseWriter, err error) int {
|
|
||||||
message := fmt.Sprintf("Unauthorized,%v", err)
|
|
||||||
w.Header().Add("WWW-Authenticate", message)
|
|
||||||
log.Println(message)
|
|
||||||
return http.StatusUnauthorized
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Auth) ExtractToken(r *http.Request) (string, error) {
|
|
||||||
|
|
||||||
jwtHeader := strings.Split(r.Header.Get("Authorization"), " ")
|
|
||||||
|
|
||||||
if jwtHeader[0] == "Bearer" && len(jwtHeader) == 2 {
|
|
||||||
return jwtHeader[1], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
jwtCookie, err := r.Cookie("token")
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
return jwtCookie.Value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
jwtQuery := r.URL.Query().Get("token")
|
|
||||||
|
|
||||||
if jwtQuery != "" {
|
|
||||||
return jwtQuery, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("no token found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Auth) ProvideKey(token *jwt.Token) (interface{}, error) {
|
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
|
|
||||||
return h.Rule.Secret, nil
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright 2019 The KubeSphere Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package authenticate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/internal"
|
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
|
||||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Setup(c *caddy.Controller) error {
|
|
||||||
|
|
||||||
rule, err := parse(c)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stopCh := make(chan struct{})
|
|
||||||
|
|
||||||
c.OnStartup(func() error {
|
|
||||||
rule.RedisClient, err = cache.NewRedisClient(rule.RedisOptions, stopCh)
|
|
||||||
// ensure redis is connected when startup
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println("Authenticate middleware is initiated")
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
c.OnShutdown(func() error {
|
|
||||||
close(stopCh)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
|
||||||
return &Auth{Next: next, Rule: rule}
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(c *caddy.Controller) (*Rule, error) {
|
|
||||||
|
|
||||||
rule := &Rule{}
|
|
||||||
rule.ExclusionRules = make([]internal.ExclusionRule, 0)
|
|
||||||
if c.Next() {
|
|
||||||
args := c.RemainingArgs()
|
|
||||||
switch len(args) {
|
|
||||||
case 0:
|
|
||||||
for c.NextBlock() {
|
|
||||||
switch c.Val() {
|
|
||||||
case "path":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
rule.Path = c.Val()
|
|
||||||
|
|
||||||
if c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
case "token-idle-timeout":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
if timeout, err := time.ParseDuration(c.Val()); err != nil {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
} else {
|
|
||||||
rule.TokenIdleTimeout = timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
case "redis-url":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
options := &cache.Options{Host: c.Val()}
|
|
||||||
|
|
||||||
if err := options.Validate(); len(err) > 0 {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
} else {
|
|
||||||
rule.RedisOptions = options
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
case "secret":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
rule.Secret = []byte(c.Val())
|
|
||||||
|
|
||||||
if c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
case "except":
|
|
||||||
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
method := c.Val()
|
|
||||||
|
|
||||||
if !sliceutil.HasString(internal.HttpMethods, method) {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
for c.NextArg() {
|
|
||||||
path := c.Val()
|
|
||||||
rule.ExclusionRules = append(rule.ExclusionRules, internal.ExclusionRule{Method: method, Path: path})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Next() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
if rule.RedisOptions == nil {
|
|
||||||
return nil, c.Err("redis-url must be specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
return rule, nil
|
|
||||||
}
|
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright 2019 The KubeSphere Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package authentication
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/client-go/informers"
|
|
||||||
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/internal"
|
|
||||||
"kubesphere.io/kubesphere/pkg/models/iam"
|
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
|
||||||
"k8s.io/api/rbac/v1"
|
|
||||||
k8serr "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Authentication struct {
|
|
||||||
Rule *Rule
|
|
||||||
Next httpserver.Handler
|
|
||||||
informerFactory informers.SharedInformerFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule struct {
|
|
||||||
Path string
|
|
||||||
KubernetesOptions *k8s.KubernetesOptions
|
|
||||||
ExclusionRules []internal.ExclusionRule
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
|
||||||
|
|
||||||
if httpserver.Path(r.URL.Path).Matches(c.Rule.Path) {
|
|
||||||
|
|
||||||
for _, rule := range c.Rule.ExclusionRules {
|
|
||||||
if httpserver.Path(r.URL.Path).Matches(rule.Path) && (rule.Method == internal.AllMethod || r.Method == rule.Method) {
|
|
||||||
return c.Next.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs, err := getAuthorizerAttributes(r.Context())
|
|
||||||
|
|
||||||
// without authenticate, no requestInfo found in the context
|
|
||||||
if err != nil {
|
|
||||||
return c.Next.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
permitted, err := c.permissionValidate(attrs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !permitted {
|
|
||||||
err = k8serr.NewForbidden(schema.GroupResource{Group: attrs.GetAPIGroup(), Resource: attrs.GetResource()}, attrs.GetName(), fmt.Errorf("permission undefined"))
|
|
||||||
return handleForbidden(w, err), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Next.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleForbidden(w http.ResponseWriter, err error) int {
|
|
||||||
message := fmt.Sprintf("Forbidden,%s", err.Error())
|
|
||||||
w.Header().Add("WWW-Authenticate", message)
|
|
||||||
log.Println(message)
|
|
||||||
return http.StatusForbidden
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Authentication) permissionValidate(attrs authorizer.Attributes) (bool, error) {
|
|
||||||
|
|
||||||
if attrs.GetResource() == "users" && attrs.GetUser().GetName() == attrs.GetName() {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
permitted, err := c.clusterRoleValidate(attrs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("lister error", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if permitted {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if attrs.GetNamespace() != "" {
|
|
||||||
permitted, err = c.roleValidate(attrs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("lister error", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if permitted {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Authentication) roleValidate(attrs authorizer.Attributes) (bool, error) {
|
|
||||||
roleBindingLister := c.informerFactory.Rbac().V1().RoleBindings().Lister()
|
|
||||||
roleLister := c.informerFactory.Rbac().V1().Roles().Lister()
|
|
||||||
roleBindings, err := roleBindingLister.RoleBindings(attrs.GetNamespace()).List(labels.Everything())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fullSource := attrs.GetResource()
|
|
||||||
|
|
||||||
if attrs.GetSubresource() != "" {
|
|
||||||
fullSource = fullSource + "/" + attrs.GetSubresource()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, roleBinding := range roleBindings {
|
|
||||||
if iam.ContainsUser(roleBinding.Subjects, attrs.GetUser().GetName()) {
|
|
||||||
role, err := roleLister.Roles(attrs.GetNamespace()).Get(roleBinding.RoleRef.Name)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if k8serr.IsNotFound(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rule := range role.Rules {
|
|
||||||
if ruleMatchesRequest(rule, attrs.GetAPIGroup(), "", attrs.GetResource(), attrs.GetSubresource(), attrs.GetName(), attrs.GetVerb()) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Authentication) clusterRoleValidate(attrs authorizer.Attributes) (bool, error) {
|
|
||||||
clusterRoleBindingLister := c.informerFactory.Rbac().V1().ClusterRoleBindings().Lister()
|
|
||||||
clusterRoleBindings, err := clusterRoleBindingLister.List(labels.Everything())
|
|
||||||
clusterRoleLister := c.informerFactory.Rbac().V1().ClusterRoles().Lister()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, clusterRoleBinding := range clusterRoleBindings {
|
|
||||||
|
|
||||||
if iam.ContainsUser(clusterRoleBinding.Subjects, attrs.GetUser().GetName()) {
|
|
||||||
clusterRole, err := clusterRoleLister.Get(clusterRoleBinding.RoleRef.Name)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if k8serr.IsNotFound(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rule := range clusterRole.Rules {
|
|
||||||
if attrs.IsResourceRequest() {
|
|
||||||
if ruleMatchesRequest(rule, attrs.GetAPIGroup(), "", attrs.GetResource(), attrs.GetSubresource(), attrs.GetName(), attrs.GetVerb()) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ruleMatchesRequest(rule, "", attrs.GetPath(), "", "", "", attrs.GetVerb()) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ruleMatchesResources(rule v1.PolicyRule, apiGroup string, resource string, subresource string, resourceName string) bool {
|
|
||||||
|
|
||||||
if resource == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sliceutil.HasString(rule.APIGroups, apiGroup) && !sliceutil.HasString(rule.APIGroups, v1.ResourceAll) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(rule.ResourceNames) > 0 && !sliceutil.HasString(rule.ResourceNames, resourceName) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
combinedResource := resource
|
|
||||||
|
|
||||||
if subresource != "" {
|
|
||||||
combinedResource = combinedResource + "/" + subresource
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, res := range rule.Resources {
|
|
||||||
|
|
||||||
// match "*"
|
|
||||||
if res == v1.ResourceAll || res == combinedResource {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// match "*/subresource"
|
|
||||||
if len(subresource) > 0 && strings.HasPrefix(res, "*/") && subresource == strings.TrimLeft(res, "*/") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// match "resource/*"
|
|
||||||
if strings.HasSuffix(res, "/*") && resource == strings.TrimRight(res, "/*") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func ruleMatchesRequest(rule v1.PolicyRule, apiGroup string, nonResourceURL string, resource string, subresource string, resourceName string, verb string) bool {
|
|
||||||
|
|
||||||
if !sliceutil.HasString(rule.Verbs, verb) && !sliceutil.HasString(rule.Verbs, v1.VerbAll) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if nonResourceURL == "" {
|
|
||||||
return ruleMatchesResources(rule, apiGroup, resource, subresource, resourceName)
|
|
||||||
} else {
|
|
||||||
return ruleMatchesNonResource(rule, nonResourceURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ruleMatchesNonResource(rule v1.PolicyRule, nonResourceURL string) bool {
|
|
||||||
|
|
||||||
if nonResourceURL == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, spec := range rule.NonResourceURLs {
|
|
||||||
if pathMatches(nonResourceURL, spec) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathMatches(path, spec string) bool {
|
|
||||||
if spec == "*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if spec == path {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(spec, "*") && strings.HasPrefix(path, strings.TrimRight(spec, "*")) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error) {
|
|
||||||
attribs := authorizer.AttributesRecord{}
|
|
||||||
|
|
||||||
user, ok := request.UserFrom(ctx)
|
|
||||||
if ok {
|
|
||||||
attribs.User = user
|
|
||||||
}
|
|
||||||
|
|
||||||
requestInfo, found := request.RequestInfoFrom(ctx)
|
|
||||||
if !found {
|
|
||||||
return nil, errors.New("no RequestInfo found in the context")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start with common attributes that apply to resource and non-resource requests
|
|
||||||
attribs.ResourceRequest = requestInfo.IsResourceRequest
|
|
||||||
attribs.Path = requestInfo.Path
|
|
||||||
attribs.Verb = requestInfo.Verb
|
|
||||||
|
|
||||||
attribs.APIGroup = requestInfo.APIGroup
|
|
||||||
attribs.APIVersion = requestInfo.APIVersion
|
|
||||||
attribs.Resource = requestInfo.Resource
|
|
||||||
attribs.Subresource = requestInfo.Subresource
|
|
||||||
attribs.Namespace = requestInfo.Namespace
|
|
||||||
attribs.Name = requestInfo.Name
|
|
||||||
|
|
||||||
return &attribs, nil
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright 2019 The KubeSphere Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package authentication
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/mholt/caddy"
|
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
|
||||||
"k8s.io/klog"
|
|
||||||
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/internal"
|
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
|
||||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/informers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Setup is called by Caddy to parse the config block
|
|
||||||
func Setup(c *caddy.Controller) error {
|
|
||||||
|
|
||||||
rule, err := parse(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rule.KubernetesOptions == nil && rule.KubernetesOptions.KubeConfig == "" {
|
|
||||||
klog.Warning("no kubeconfig provided, will use in cluster config, this may not work")
|
|
||||||
}
|
|
||||||
|
|
||||||
kubeClient, err := k8s.NewKubernetesClient(rule.KubernetesOptions)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
informerFactory := informers.NewInformerFactories(kubeClient.Kubernetes(), nil, nil, nil)
|
|
||||||
|
|
||||||
stopChan := make(chan struct{}, 0)
|
|
||||||
c.OnStartup(func() error {
|
|
||||||
informerFactory.KubernetesSharedInformerFactory().Rbac().V1().Roles().Lister()
|
|
||||||
informerFactory.KubernetesSharedInformerFactory().Rbac().V1().RoleBindings().Lister()
|
|
||||||
informerFactory.KubernetesSharedInformerFactory().Rbac().V1().ClusterRoles().Lister()
|
|
||||||
informerFactory.KubernetesSharedInformerFactory().Rbac().V1().ClusterRoleBindings().Lister()
|
|
||||||
informerFactory.KubernetesSharedInformerFactory().Start(stopChan)
|
|
||||||
informerFactory.KubernetesSharedInformerFactory().WaitForCacheSync(stopChan)
|
|
||||||
fmt.Println("Authentication middleware is initiated")
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
c.OnShutdown(func() error {
|
|
||||||
close(stopChan)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
|
||||||
return &Authentication{Next: next, Rule: rule, informerFactory: informerFactory.KubernetesSharedInformerFactory()}
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(c *caddy.Controller) (*Rule, error) {
|
|
||||||
|
|
||||||
rule := &Rule{}
|
|
||||||
rule.ExclusionRules = make([]internal.ExclusionRule, 0)
|
|
||||||
if c.Next() {
|
|
||||||
args := c.RemainingArgs()
|
|
||||||
switch len(args) {
|
|
||||||
case 0:
|
|
||||||
for c.NextBlock() {
|
|
||||||
switch c.Val() {
|
|
||||||
case "path":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return rule, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
rule.Path = c.Val()
|
|
||||||
|
|
||||||
if c.NextArg() {
|
|
||||||
return rule, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
case "except":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
method := c.Val()
|
|
||||||
|
|
||||||
if !sliceutil.HasString(internal.HttpMethods, method) {
|
|
||||||
return nil, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
for c.NextArg() {
|
|
||||||
path := c.Val()
|
|
||||||
rule.ExclusionRules = append(rule.ExclusionRules, internal.ExclusionRule{Method: method, Path: path})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
rule.Path = args[0]
|
|
||||||
if c.NextBlock() {
|
|
||||||
return rule, c.ArgErr()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return rule, c.ArgErr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Next() {
|
|
||||||
return rule, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
return rule, nil
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright 2019 The KubeSphere Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package authenticate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Setup(c *caddy.Controller) error {
|
|
||||||
|
|
||||||
handler, err := parse(c)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.OnStartup(func() error {
|
|
||||||
fmt.Println("Swagger middleware is initiated")
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
|
||||||
return &Swagger{Next: next, Handler: handler}
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func parse(c *caddy.Controller) (Handler, error) {
|
|
||||||
|
|
||||||
handler := Handler{URL: "/swagger-ui", FilePath: "/var/static/swagger-ui"}
|
|
||||||
|
|
||||||
if c.Next() {
|
|
||||||
args := c.RemainingArgs()
|
|
||||||
switch len(args) {
|
|
||||||
case 0:
|
|
||||||
for c.NextBlock() {
|
|
||||||
switch c.Val() {
|
|
||||||
case "url":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return handler, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.URL = c.Val()
|
|
||||||
|
|
||||||
if c.NextArg() {
|
|
||||||
return handler, c.ArgErr()
|
|
||||||
}
|
|
||||||
case "filePath":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return handler, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.FilePath = c.Val()
|
|
||||||
|
|
||||||
if c.NextArg() {
|
|
||||||
return handler, c.ArgErr()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return handler, c.ArgErr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return handler, c.ArgErr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Next() {
|
|
||||||
return handler, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.Handler = http.StripPrefix(handler.URL, http.FileServer(http.Dir(handler.FilePath)))
|
|
||||||
|
|
||||||
return handler, nil
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright 2019 The KubeSphere Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package authenticate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Swagger struct {
|
|
||||||
Handler Handler
|
|
||||||
Next httpserver.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
type Handler struct {
|
|
||||||
URL string
|
|
||||||
FilePath string
|
|
||||||
Handler http.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Swagger) ServeHTTP(resp http.ResponseWriter, req *http.Request) (int, error) {
|
|
||||||
|
|
||||||
if httpserver.Path(req.URL.Path).Matches(h.Handler.URL) {
|
|
||||||
h.Handler.Handler.ServeHTTP(resp, req)
|
|
||||||
return http.StatusOK, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.Next.ServeHTTP(resp, req)
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package apigateway
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mholt/caddy"
|
|
||||||
|
|
||||||
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/authenticate"
|
|
||||||
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/authentication"
|
|
||||||
swagger "kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/swagger"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterPlugins() {
|
|
||||||
caddy.RegisterPlugin("swagger", caddy.Plugin{
|
|
||||||
ServerType: "http",
|
|
||||||
Action: swagger.Setup,
|
|
||||||
})
|
|
||||||
|
|
||||||
caddy.RegisterPlugin("authenticate", caddy.Plugin{
|
|
||||||
ServerType: "http",
|
|
||||||
Action: authenticate.Setup,
|
|
||||||
})
|
|
||||||
|
|
||||||
caddy.RegisterPlugin("authentication", caddy.Plugin{
|
|
||||||
ServerType: "http",
|
|
||||||
Action: authentication.Setup,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
9
pkg/apis/addtoscheme_tower_v1alpha1.go
Normal file
9
pkg/apis/addtoscheme_tower_v1alpha1.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package apis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme)
|
||||||
|
}
|
||||||
1
pkg/apis/tower/group.go
Normal file
1
pkg/apis/tower/group.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package tower
|
||||||
116
pkg/apis/tower/v1alpha1/agent_types.go
Normal file
116
pkg/apis/tower/v1alpha1/agent_types.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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 v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||||
|
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||||
|
|
||||||
|
// AgentSpec defines the desired state of Agent
|
||||||
|
type AgentSpec struct {
|
||||||
|
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||||
|
// Important: Run "make" to regenerate code after modifying this file
|
||||||
|
|
||||||
|
// Token used by agents to connect to proxy.
|
||||||
|
// +optional
|
||||||
|
Token string `json:"token,omitempty"`
|
||||||
|
|
||||||
|
// Proxy address
|
||||||
|
// +optional
|
||||||
|
Proxy string `json:"proxy,omitempty"`
|
||||||
|
|
||||||
|
// KubeAPIServerPort is the port which listens for forwarding kube-apiserver traffic
|
||||||
|
// +optional
|
||||||
|
KubernetesAPIServerPort uint16 `json:"kubernetesAPIServerPort,omitempty"`
|
||||||
|
|
||||||
|
// KubeSphereAPIServerPort is the port which listens for forwarding kubesphere apigateway traffic
|
||||||
|
// +optional
|
||||||
|
KubeSphereAPIServerPort uint16 `json:"kubesphereAPIServerPort,omitempty"`
|
||||||
|
|
||||||
|
// Indicates that the agent is paused.
|
||||||
|
// +optional
|
||||||
|
Paused bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type AgentConditionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Agent is initialized, and waiting for establishing to a proxy server
|
||||||
|
AgentInitialized AgentConditionType = "Initialized"
|
||||||
|
|
||||||
|
// Agent has successfully connected to proxy server
|
||||||
|
AgentConnected AgentConditionType = "Connected"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AgentCondition struct {
|
||||||
|
// Type of AgentCondition
|
||||||
|
Type AgentConditionType `json:"type,omitempty"`
|
||||||
|
// Status of the condition, one of True, False, Unknown.
|
||||||
|
Status v1.ConditionStatus `json:"status"`
|
||||||
|
// The last time this condition was updated.
|
||||||
|
LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
|
||||||
|
// Last time the condition transitioned from one status to another.
|
||||||
|
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
|
||||||
|
// The reason for the condition's last transition.
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
// A human readable message indicating details about the transition.
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentStatus defines the observed state of Agent
|
||||||
|
type AgentStatus struct {
|
||||||
|
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||||
|
// Important: Run "make" to regenerate code after modifying this file
|
||||||
|
|
||||||
|
// Represents the latest available observations of a agent's current state.
|
||||||
|
Conditions []AgentCondition `json:"conditions,omitempty"`
|
||||||
|
|
||||||
|
// Represents the connection quality, in ms
|
||||||
|
Ping uint64 `json:"ping,omitempty"`
|
||||||
|
|
||||||
|
// Issued new kubeconfig by proxy server
|
||||||
|
KubeConfig []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// +genclient
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
|
||||||
|
// Agent is the Schema for the agents API
|
||||||
|
type Agent struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Spec AgentSpec `json:"spec,omitempty"`
|
||||||
|
Status AgentStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// AgentList contains a list of Agent
|
||||||
|
type AgentList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty"`
|
||||||
|
Items []Agent `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SchemeBuilder.Register(&Agent{}, &AgentList{})
|
||||||
|
}
|
||||||
8
pkg/apis/tower/v1alpha1/doc.go
Normal file
8
pkg/apis/tower/v1alpha1/doc.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Package v1alpha2 contains API Schema definitions for the tower v1alpha1 API group
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
// +k8s:deepcopy-gen=package,register
|
||||||
|
// +k8s:conversion-gen=kubesphere.io/tower/pkg/apis/tower
|
||||||
|
// +k8s:defaulter-gen=TypeMeta
|
||||||
|
// +groupName=tower.kubesphere.io
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
41
pkg/apis/tower/v1alpha1/register.go
Normal file
41
pkg/apis/tower/v1alpha1/register.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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 v1alpha1 contains API Schema definitions for the tower v1alpha1 API group
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
// +k8s:deepcopy-gen=package,register
|
||||||
|
// +k8s:defaulter-gen=TypeMeta
|
||||||
|
// +groupName=tower.kubesphere.io
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// GroupVersion is group version used to register these objects
|
||||||
|
SchemeGroupVersion = schema.GroupVersion{Group: "tower.kubesphere.io", Version: "v1alpha1"}
|
||||||
|
|
||||||
|
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||||
|
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
|
||||||
|
|
||||||
|
// AddToScheme adds the types in this group-version to the given scheme.
|
||||||
|
AddToScheme = SchemeBuilder.AddToScheme
|
||||||
|
)
|
||||||
|
|
||||||
|
func Resource(resource string) schema.GroupResource {
|
||||||
|
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||||
|
}
|
||||||
137
pkg/apis/tower/v1alpha1/zz_generated.deepcopy.go
generated
Normal file
137
pkg/apis/tower/v1alpha1/zz_generated.deepcopy.go
generated
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by controller-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Agent) DeepCopyInto(out *Agent) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
out.Spec = in.Spec
|
||||||
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Agent.
|
||||||
|
func (in *Agent) DeepCopy() *Agent {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Agent)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *Agent) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AgentCondition) DeepCopyInto(out *AgentCondition) {
|
||||||
|
*out = *in
|
||||||
|
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
|
||||||
|
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentCondition.
|
||||||
|
func (in *AgentCondition) DeepCopy() *AgentCondition {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AgentCondition)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AgentList) DeepCopyInto(out *AgentList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]Agent, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentList.
|
||||||
|
func (in *AgentList) DeepCopy() *AgentList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AgentList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *AgentList) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AgentSpec) DeepCopyInto(out *AgentSpec) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSpec.
|
||||||
|
func (in *AgentSpec) DeepCopy() *AgentSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AgentSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AgentStatus) DeepCopyInto(out *AgentStatus) {
|
||||||
|
*out = *in
|
||||||
|
if in.Conditions != nil {
|
||||||
|
in, out := &in.Conditions, &out.Conditions
|
||||||
|
*out = make([]AgentCondition, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentStatus.
|
||||||
|
func (in *AgentStatus) DeepCopy() *AgentStatus {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AgentStatus)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -9,13 +9,19 @@ import (
|
|||||||
urlruntime "k8s.io/apimachinery/pkg/util/runtime"
|
urlruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||||
"k8s.io/apiserver/pkg/authentication/request/union"
|
unionauth "k8s.io/apiserver/pkg/authentication/request/union"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/basic"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken"
|
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken"
|
||||||
authenticationrequest "kubesphere.io/kubesphere/pkg/apiserver/authentication/request"
|
oauth2 "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/anonymous"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/basictoken"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/path"
|
||||||
|
unionauthorizer "kubesphere.io/kubesphere/pkg/apiserver/authorization/union"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/dispatch"
|
"kubesphere.io/kubesphere/pkg/apiserver/dispatch"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/filters"
|
"kubesphere.io/kubesphere/pkg/apiserver/filters"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||||
@@ -24,6 +30,7 @@ import (
|
|||||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2"
|
iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2"
|
||||||
loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2"
|
loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2"
|
||||||
monitoringv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha2"
|
monitoringv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha2"
|
||||||
|
"kubesphere.io/kubesphere/pkg/kapis/oauth"
|
||||||
openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1"
|
openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1"
|
||||||
operationsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/operations/v1alpha2"
|
operationsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/operations/v1alpha2"
|
||||||
resourcesv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha2"
|
resourcesv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha2"
|
||||||
@@ -31,6 +38,8 @@ import (
|
|||||||
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2"
|
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2"
|
||||||
tenantv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2"
|
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/im"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/devops"
|
"kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
||||||
@@ -66,7 +75,7 @@ type APIServer struct {
|
|||||||
//
|
//
|
||||||
Server *http.Server
|
Server *http.Server
|
||||||
|
|
||||||
AuthenticateOptions *iam.AuthenticationOptions
|
AuthenticateOptions *auth.AuthenticationOptions
|
||||||
|
|
||||||
// webservice container, where all webservice defines
|
// webservice container, where all webservice defines
|
||||||
container *restful.Container
|
container *restful.Container
|
||||||
@@ -101,8 +110,6 @@ type APIServer struct {
|
|||||||
|
|
||||||
//
|
//
|
||||||
LdapClient ldap.Interface
|
LdapClient ldap.Interface
|
||||||
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *APIServer) PrepareRun() error {
|
func (s *APIServer) PrepareRun() error {
|
||||||
@@ -140,6 +147,7 @@ func (s *APIServer) installKubeSphereAPIs() {
|
|||||||
urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database()))
|
urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database()))
|
||||||
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
|
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
|
||||||
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.AuthenticateOptions))
|
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.AuthenticateOptions))
|
||||||
|
urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.AuthenticateOptions, s.CacheClient), &oauth2.SimpleConfigManager{}))
|
||||||
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
|
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,19 +182,23 @@ func (s *APIServer) buildHandlerChain() {
|
|||||||
GrouplessAPIPrefixes: sets.NewString("api", "kapi"),
|
GrouplessAPIPrefixes: sets.NewString("api", "kapi"),
|
||||||
}
|
}
|
||||||
|
|
||||||
failed := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
})
|
|
||||||
|
|
||||||
handler := s.Server.Handler
|
handler := s.Server.Handler
|
||||||
|
|
||||||
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})
|
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})
|
||||||
handler = filters.WithMultipleClusterDispatcher(handler, dispatch.DefaultClusterDispatch)
|
handler = filters.WithMultipleClusterDispatcher(handler, dispatch.NewClusterDispatch(s.InformerFactory.KubeSphereSharedInformerFactory().Tower().V1alpha1().Agents().Lister()))
|
||||||
handler = filters.WithAuthorization(handler, authorizerfactory.NewAlwaysAllowAuthorizer())
|
|
||||||
|
|
||||||
authn := union.New(&authenticationrequest.AnonymousAuthenticator{}, bearertoken.New(jwttoken.NewTokenAuthenticator(s.CacheClient, s.AuthenticateOptions.JwtSecret)))
|
excludedPaths := []string{"/oauth/authorize", "/oauth/token"}
|
||||||
handler = filters.WithAuthentication(handler, authn, failed)
|
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
|
||||||
|
authorizer := unionauthorizer.New(pathAuthorizer,
|
||||||
|
authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator()))
|
||||||
|
handler = filters.WithAuthorization(handler, authorizer)
|
||||||
|
|
||||||
|
authn := unionauth.New(anonymous.NewAuthenticator(),
|
||||||
|
basictoken.New(basic.NewBasicAuthenticator(im.NewFakeOperator())),
|
||||||
|
bearertoken.New(jwttoken.NewTokenAuthenticator(
|
||||||
|
token.NewJwtTokenIssuer(token.DefaultIssuerName, s.AuthenticateOptions, s.CacheClient))))
|
||||||
|
handler = filters.WithAuthentication(handler, authn)
|
||||||
handler = filters.WithRequestInfo(handler, requestInfoResolver)
|
handler = filters.WithRequestInfo(handler, requestInfoResolver)
|
||||||
|
|
||||||
s.Server.Handler = handler
|
s.Server.Handler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +206,7 @@ func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error {
|
|||||||
klog.V(0).Info("Start cache objects")
|
klog.V(0).Info("Start cache objects")
|
||||||
|
|
||||||
discoveryClient := s.KubernetesClient.Kubernetes().Discovery()
|
discoveryClient := s.KubernetesClient.Kubernetes().Discovery()
|
||||||
apiResourcesList, err := discoveryClient.ServerResources()
|
_, apiResourcesList, err := discoveryClient.ServerGroupsAndResources()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -263,6 +275,7 @@ func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error {
|
|||||||
|
|
||||||
ksGVRs := []schema.GroupVersionResource{
|
ksGVRs := []schema.GroupVersionResource{
|
||||||
{Group: "tenant.kubesphere.io", Version: "v1alpha1", Resource: "workspaces"},
|
{Group: "tenant.kubesphere.io", Version: "v1alpha1", Resource: "workspaces"},
|
||||||
|
{Group: "tower.kubesphere.io", Version: "v1alpha1", Resource: "agents"},
|
||||||
}
|
}
|
||||||
|
|
||||||
devopsGVRs := []schema.GroupVersionResource{
|
devopsGVRs := []schema.GroupVersionResource{
|
||||||
|
|||||||
58
pkg/apiserver/authentication/authenticators/basic/basic.go
Normal file
58
pkg/apiserver/authentication/authenticators/basic/basic.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"kubesphere.io/kubesphere/pkg/models/iam/im"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic.
|
||||||
|
// TokenAuthenticator will retrieve user info from cache by given token. If empty or invalid token
|
||||||
|
// was given, authenticator will still give passed response at the condition user will be user.Anonymous
|
||||||
|
// and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
|
||||||
|
// because some resources are public accessible.
|
||||||
|
type basicAuthenticator struct {
|
||||||
|
im im.IdentityManagementInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBasicAuthenticator(im im.IdentityManagementInterface) authenticator.Password {
|
||||||
|
return &basicAuthenticator{
|
||||||
|
im: im,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
||||||
|
|
||||||
|
providedUser, err := t.im.Authenticate(username, password)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &authenticator.Response{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Name: providedUser.GetName(),
|
||||||
|
UID: providedUser.GetUID(),
|
||||||
|
Groups: []string{user.AllAuthenticated},
|
||||||
|
},
|
||||||
|
}, true, nil
|
||||||
|
}
|
||||||
@@ -2,56 +2,37 @@ package jwttoken
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"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/api/iam/token"
|
token2 "kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
|
||||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errTokenExpired = errors.New("expired token")
|
|
||||||
|
|
||||||
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic.
|
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic.
|
||||||
// TokenAuthenticator will retrieve user info from cache by given token. If empty or invalid token
|
// TokenAuthenticator will retrieve user info from cache by given token. If empty or invalid token
|
||||||
// was given, authenticator will still give passed response at the condition user will be user.Anonymous
|
// was given, authenticator will still give passed response at the condition user will be user.Anonymous
|
||||||
// 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 {
|
||||||
cacheClient cache.Interface
|
jwtTokenIssuer token2.Issuer
|
||||||
jwtTokenIssuer token.Issuer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTokenAuthenticator(cacheClient cache.Interface, jwtSecret string) authenticator.Token {
|
func NewTokenAuthenticator(issuer token2.Issuer) authenticator.Token {
|
||||||
return &tokenAuthenticator{
|
return &tokenAuthenticator{
|
||||||
cacheClient: cacheClient,
|
jwtTokenIssuer: issuer,
|
||||||
jwtTokenIssuer: token.NewJwtTokenIssuer(token.DefaultIssuerName, []byte(jwtSecret)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
|
func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
|
||||||
providedUser, err := t.jwtTokenIssuer.Verify(token)
|
providedUser, _, err := t.jwtTokenIssuer.Verify(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = t.cacheClient.Get(tokenKeyForUsername(providedUser.Name(), token))
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, errTokenExpired
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should we need to refresh token?
|
|
||||||
|
|
||||||
return &authenticator.Response{
|
return &authenticator.Response{
|
||||||
User: &user.DefaultInfo{
|
User: &user.DefaultInfo{
|
||||||
Name: providedUser.Name(),
|
Name: providedUser.GetName(),
|
||||||
UID: providedUser.UID(),
|
UID: providedUser.GetUID(),
|
||||||
Groups: []string{user.AllAuthenticated},
|
Groups: []string{user.AllAuthenticated},
|
||||||
},
|
},
|
||||||
}, true, nil
|
}, true, nil
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func tokenKeyForUsername(username, token string) string {
|
|
||||||
return fmt.Sprintf("kubesphere:users:%s:token:%s", username, token)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2019 The KubeSphere 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.
|
||||||
@@ -16,17 +16,15 @@
|
|||||||
* /
|
* /
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package internal
|
package oauth
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"errors"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
const AllMethod = "*"
|
var ConfigNotFound = errors.New("config not found")
|
||||||
|
|
||||||
var HttpMethods = []string{AllMethod, http.MethodPost, http.MethodDelete,
|
type Configuration interface {
|
||||||
http.MethodPatch, http.MethodPut, http.MethodGet, http.MethodOptions, http.MethodConnect}
|
Load(clientId string) (*oauth2.Config, error)
|
||||||
|
|
||||||
// Path exclusion rule
|
|
||||||
type ExclusionRule struct {
|
|
||||||
Method string
|
|
||||||
Path string
|
|
||||||
}
|
}
|
||||||
37
pkg/apiserver/authentication/oauth/simple_config.go
Normal file
37
pkg/apiserver/authentication/oauth/simple_config.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 The KubeSphere Authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* /
|
||||||
|
*/
|
||||||
|
|
||||||
|
package oauth
|
||||||
|
|
||||||
|
import "golang.org/x/oauth2"
|
||||||
|
|
||||||
|
type SimpleConfigManager struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SimpleConfigManager) Load(clientId string) (*oauth2.Config, error) {
|
||||||
|
if clientId == "kubesphere-console-client" {
|
||||||
|
return &oauth2.Config{
|
||||||
|
ClientID: "8b21fef43889a28f2bd6",
|
||||||
|
ClientSecret: "xb21fef43889a28f2bd6",
|
||||||
|
Endpoint: oauth2.Endpoint{AuthURL: "http://ks-apiserver.kubesphere-system.svc/oauth/authorize", TokenURL: "http://ks-apiserver.kubesphere.io/oauth/token"},
|
||||||
|
RedirectURL: "http://ks-console.kubesphere-system.svc/oauth/token/implicit",
|
||||||
|
Scopes: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, ConfigNotFound
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package request
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AnonymousAuthenticator struct{}
|
|
||||||
|
|
||||||
func (a *AnonymousAuthenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
|
||||||
auth := strings.TrimSpace(req.Header.Get("Authorization"))
|
|
||||||
if auth == "" {
|
|
||||||
return &authenticator.Response{
|
|
||||||
User: &user.DefaultInfo{
|
|
||||||
Name: user.Anonymous,
|
|
||||||
UID: "",
|
|
||||||
Groups: []string{user.AllUnauthenticated},
|
|
||||||
},
|
|
||||||
}, true, nil
|
|
||||||
}
|
|
||||||
return nil, false, nil
|
|
||||||
}
|
|
||||||
46
pkg/apiserver/authentication/request/anonymous/anonymous.go
Normal file
46
pkg/apiserver/authentication/request/anonymous/anonymous.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 anonymous
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Authenticator struct{}
|
||||||
|
|
||||||
|
func NewAuthenticator() authenticator.Request {
|
||||||
|
return &Authenticator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||||
|
auth := strings.TrimSpace(req.Header.Get("Authorization"))
|
||||||
|
if auth == "" {
|
||||||
|
return &authenticator.Response{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Name: user.Anonymous,
|
||||||
|
UID: "",
|
||||||
|
Groups: []string{user.AllUnauthenticated},
|
||||||
|
},
|
||||||
|
}, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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 basictoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Authenticator struct {
|
||||||
|
auth authenticator.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(auth authenticator.Password) *Authenticator {
|
||||||
|
return &Authenticator{auth}
|
||||||
|
}
|
||||||
|
|
||||||
|
var invalidToken = errors.New("invalid basic token")
|
||||||
|
|
||||||
|
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||||
|
|
||||||
|
username, password, ok := req.BasicAuth()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, ok, err := a.auth.AuthenticatePassword(req.Context(), username, password)
|
||||||
|
// if we authenticated successfully, go ahead and remove the bearer token so that no one
|
||||||
|
// is ever tempted to use it inside of the API server
|
||||||
|
if ok {
|
||||||
|
req.Header.Del("Authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the token authenticator didn't error, provide a default error
|
||||||
|
if !ok && err == nil {
|
||||||
|
err = invalidToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, ok, err
|
||||||
|
}
|
||||||
31
pkg/apiserver/authentication/token/issuer.go
Normal file
31
pkg/apiserver/authentication/token/issuer.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 token
|
||||||
|
|
||||||
|
// Issuer issues token to user, tokens are required to perform mutating requests to resources
|
||||||
|
type Issuer interface {
|
||||||
|
// IssueTo issues a token a User, return error if issuing process failed
|
||||||
|
IssueTo(User) (string, *Claims, error)
|
||||||
|
|
||||||
|
// Verify verifies a token, and return a User if it's a valid token, otherwise return error
|
||||||
|
Verify(string) (User, *Claims, error)
|
||||||
|
|
||||||
|
// Revoke a token,
|
||||||
|
Revoke(token string) error
|
||||||
|
}
|
||||||
124
pkg/apiserver/authentication/token/jwt.go
Normal file
124
pkg/apiserver/authentication/token/jwt.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dgrijalva/jwt-go"
|
||||||
|
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||||
|
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||||
|
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||||
|
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultIssuerName = "kubesphere"
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidToken = errors.New("invalid token")
|
||||||
|
errTokenExpired = errors.New("expired token")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Claims struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
UID string `json:"uid"`
|
||||||
|
// Currently, we are not using any field in jwt.StandardClaims
|
||||||
|
jwt.StandardClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
type jwtTokenIssuer struct {
|
||||||
|
name string
|
||||||
|
options *auth.AuthenticationOptions
|
||||||
|
cache cache.Interface
|
||||||
|
keyFunc jwt.Keyfunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *jwtTokenIssuer) Verify(tokenString string) (User, *Claims, error) {
|
||||||
|
if len(tokenString) == 0 {
|
||||||
|
return nil, nil, errInvalidToken
|
||||||
|
}
|
||||||
|
_, err := s.cache.Get(tokenCacheKey(tokenString))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == cache.ErrNoSuchKey {
|
||||||
|
return nil, nil, errTokenExpired
|
||||||
|
}
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
clm := &Claims{}
|
||||||
|
|
||||||
|
_, err = jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &iam.User{Name: clm.Username, UID: clm.UID}, clm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) {
|
||||||
|
clm := &Claims{
|
||||||
|
Username: user.GetName(),
|
||||||
|
UID: user.GetUID(),
|
||||||
|
StandardClaims: jwt.StandardClaims{
|
||||||
|
IssuedAt: time.Now().Unix(),
|
||||||
|
Issuer: s.name,
|
||||||
|
NotBefore: time.Now().Unix(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.options.TokenExpiration > 0 {
|
||||||
|
clm.ExpiresAt = clm.IssuedAt + int64(s.options.TokenExpiration.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, clm)
|
||||||
|
|
||||||
|
tokenString, err := token.SignedString([]byte(s.options.JwtSecret))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.cache.Set(tokenCacheKey(tokenString), tokenString, s.options.TokenExpiration)
|
||||||
|
|
||||||
|
return tokenString, clm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *jwtTokenIssuer) Revoke(token string) error {
|
||||||
|
return s.cache.Del(tokenCacheKey(token))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJwtTokenIssuer(issuerName string, options *auth.AuthenticationOptions, cache cache.Interface) Issuer {
|
||||||
|
return &jwtTokenIssuer{
|
||||||
|
name: issuerName,
|
||||||
|
options: options,
|
||||||
|
cache: cache,
|
||||||
|
keyFunc: func(token *jwt.Token) (i interface{}, err error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
|
||||||
|
return []byte(options.JwtSecret), nil
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenCacheKey(token string) string {
|
||||||
|
return fmt.Sprintf("kubesphere:tokens:%s", token)
|
||||||
|
}
|
||||||
72
pkg/apiserver/authentication/token/jwt_test.go
Normal file
72
pkg/apiserver/authentication/token/jwt_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||||
|
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||||
|
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJwtTokenIssuer(t *testing.T) {
|
||||||
|
options := auth.NewAuthenticateOptions()
|
||||||
|
options.JwtSecret = "kubesphere"
|
||||||
|
issuer := NewJwtTokenIssuer(DefaultIssuerName, options, cache.NewSimpleCache())
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
name string
|
||||||
|
uid string
|
||||||
|
email string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "admin",
|
||||||
|
uid: "b8be6edd-2c92-4535-9b2a-df6326474458",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bar",
|
||||||
|
uid: "b8be6edd-2c92-4535-9b2a-df6326474452",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
user := &iam.User{
|
||||||
|
Name: testCase.name,
|
||||||
|
UID: testCase.uid,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(testCase.description, func(t *testing.T) {
|
||||||
|
token, _, err := issuer.IssueTo(user)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, _, err := issuer.Verify(token)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(user, got); len(diff) != 0 {
|
||||||
|
t.Errorf("%T differ (-got, +expected), %s", user, diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
package token
|
|
||||||
27
pkg/apiserver/authentication/token/user.go
Normal file
27
pkg/apiserver/authentication/token/user.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 token
|
||||||
|
|
||||||
|
type User interface {
|
||||||
|
// Name
|
||||||
|
GetName() string
|
||||||
|
|
||||||
|
// UID
|
||||||
|
GetUID() string
|
||||||
|
}
|
||||||
182
pkg/apiserver/authorization/authorizer/interfaces.go
Normal file
182
pkg/apiserver/authorization/authorizer/interfaces.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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 authorizer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attributes is an interface used by an Authorizer to get information about a request
|
||||||
|
// that is used to make an authorization decision.
|
||||||
|
type Attributes interface {
|
||||||
|
// GetUser returns the user.Info object to authorize
|
||||||
|
GetUser() user.Info
|
||||||
|
|
||||||
|
// GetVerb returns the kube verb associated with API requests (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy),
|
||||||
|
// or the lowercased HTTP verb associated with non-API requests (this includes get, put, post, patch, and delete)
|
||||||
|
GetVerb() string
|
||||||
|
|
||||||
|
// When IsReadOnly() == true, the request has no side effects, other than
|
||||||
|
// caching, logging, and other incidentals.
|
||||||
|
IsReadOnly() bool
|
||||||
|
|
||||||
|
// Indicates whether or not the request should be handled by kubernetes or kubesphere
|
||||||
|
IsKubernetesRequest() bool
|
||||||
|
|
||||||
|
// The cluster of the object, if a request is for a REST object.
|
||||||
|
GetCluster() string
|
||||||
|
|
||||||
|
// The workspace of the object, if a request is for a REST object.
|
||||||
|
GetWorkspace() string
|
||||||
|
|
||||||
|
// The namespace of the object, if a request is for a REST object.
|
||||||
|
GetNamespace() string
|
||||||
|
|
||||||
|
// The kind of object, if a request is for a REST object.
|
||||||
|
GetResource() string
|
||||||
|
|
||||||
|
// GetSubresource returns the subresource being requested, if present
|
||||||
|
GetSubresource() string
|
||||||
|
|
||||||
|
// GetName returns the name of the object as parsed off the request. This will not be present for all request types, but
|
||||||
|
// will be present for: get, update, delete
|
||||||
|
GetName() string
|
||||||
|
|
||||||
|
// The group of the resource, if a request is for a REST object.
|
||||||
|
GetAPIGroup() string
|
||||||
|
|
||||||
|
// GetAPIVersion returns the version of the group requested, if a request is for a REST object.
|
||||||
|
GetAPIVersion() string
|
||||||
|
|
||||||
|
// IsResourceRequest returns true for requests to API resources, like /api/v1/nodes,
|
||||||
|
// and false for non-resource endpoints like /api, /healthz
|
||||||
|
IsResourceRequest() bool
|
||||||
|
|
||||||
|
// GetPath returns the path of the request
|
||||||
|
GetPath() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorizer makes an authorization decision based on information gained by making
|
||||||
|
// zero or more calls to methods of the Attributes interface. It returns nil when an action is
|
||||||
|
// authorized, otherwise it returns an error.
|
||||||
|
type Authorizer interface {
|
||||||
|
Authorize(a Attributes) (authorized Decision, reason string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthorizerFunc func(a Attributes) (Decision, string, error)
|
||||||
|
|
||||||
|
func (f AuthorizerFunc) Authorize(a Attributes) (Decision, string, error) {
|
||||||
|
return f(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
|
||||||
|
type RuleResolver interface {
|
||||||
|
// RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
|
||||||
|
RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestAttributesGetter provides a function that extracts Attributes from an http.Request
|
||||||
|
type RequestAttributesGetter interface {
|
||||||
|
GetRequestAttributes(user.Info, *http.Request) Attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttributesRecord implements Attributes interface.
|
||||||
|
type AttributesRecord struct {
|
||||||
|
User user.Info
|
||||||
|
Verb string
|
||||||
|
Cluster string
|
||||||
|
Workspace string
|
||||||
|
Namespace string
|
||||||
|
APIGroup string
|
||||||
|
APIVersion string
|
||||||
|
Resource string
|
||||||
|
Subresource string
|
||||||
|
Name string
|
||||||
|
KubernetesRequest bool
|
||||||
|
ResourceRequest bool
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetUser() user.Info {
|
||||||
|
return a.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetVerb() string {
|
||||||
|
return a.Verb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) IsReadOnly() bool {
|
||||||
|
return a.Verb == "get" || a.Verb == "list" || a.Verb == "watch"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetCluster() string {
|
||||||
|
return a.Cluster
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetWorkspace() string {
|
||||||
|
return a.Workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetNamespace() string {
|
||||||
|
return a.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetResource() string {
|
||||||
|
return a.Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetSubresource() string {
|
||||||
|
return a.Subresource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetName() string {
|
||||||
|
return a.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetAPIGroup() string {
|
||||||
|
return a.APIGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetAPIVersion() string {
|
||||||
|
return a.APIVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) IsResourceRequest() bool {
|
||||||
|
return a.ResourceRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) IsKubernetesRequest() bool {
|
||||||
|
return a.KubernetesRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetPath() string {
|
||||||
|
return a.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
type Decision int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DecisionDeny means that an authorizer decided to deny the action.
|
||||||
|
DecisionDeny Decision = iota
|
||||||
|
// DecisionAllow means that an authorizer decided to allow the action.
|
||||||
|
DecisionAllow
|
||||||
|
// DecisionNoOpionion means that an authorizer has no opinion on whether
|
||||||
|
// to allow or deny an action.
|
||||||
|
DecisionNoOpinion
|
||||||
|
)
|
||||||
73
pkg/apiserver/authorization/authorizer/rule.go
Normal file
73
pkg/apiserver/authorization/authorizer/rule.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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 authorizer
|
||||||
|
|
||||||
|
type ResourceRuleInfo interface {
|
||||||
|
// GetVerbs returns a list of kubernetes resource API verbs.
|
||||||
|
GetVerbs() []string
|
||||||
|
// GetAPIGroups return the names of the APIGroup that contains the resources.
|
||||||
|
GetAPIGroups() []string
|
||||||
|
// GetResources return a list of resources the rule applies to.
|
||||||
|
GetResources() []string
|
||||||
|
// GetResourceNames return a white list of names that the rule applies to.
|
||||||
|
GetResourceNames() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultResourceRuleInfo holds information that describes a rule for the resource
|
||||||
|
type DefaultResourceRuleInfo struct {
|
||||||
|
Verbs []string
|
||||||
|
APIGroups []string
|
||||||
|
Resources []string
|
||||||
|
ResourceNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetVerbs() []string {
|
||||||
|
return i.Verbs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetAPIGroups() []string {
|
||||||
|
return i.APIGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetResources() []string {
|
||||||
|
return i.Resources
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetResourceNames() []string {
|
||||||
|
return i.ResourceNames
|
||||||
|
}
|
||||||
|
|
||||||
|
type NonResourceRuleInfo interface {
|
||||||
|
// GetVerbs returns a list of kubernetes resource API verbs.
|
||||||
|
GetVerbs() []string
|
||||||
|
// GetNonResourceURLs return a set of partial urls that a user should have access to.
|
||||||
|
GetNonResourceURLs() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultNonResourceRuleInfo holds information that describes a rule for the non-resource
|
||||||
|
type DefaultNonResourceRuleInfo struct {
|
||||||
|
Verbs []string
|
||||||
|
NonResourceURLs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultNonResourceRuleInfo) GetVerbs() []string {
|
||||||
|
return i.Verbs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultNonResourceRuleInfo) GetNonResourceURLs() []string {
|
||||||
|
return i.NonResourceURLs
|
||||||
|
}
|
||||||
145
pkg/apiserver/authorization/authorizerfactory/opa.go
Normal file
145
pkg/apiserver/authorization/authorizerfactory/opa.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 authorizerfactory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/open-policy-agent/opa/rego"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
|
"kubesphere.io/kubesphere/pkg/models/iam/am"
|
||||||
|
)
|
||||||
|
|
||||||
|
type opaAuthorizer struct {
|
||||||
|
am am.AccessManagementInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make decision by request attributes
|
||||||
|
func (o *opaAuthorizer) Authorize(attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||||
|
|
||||||
|
// Make decisions based on the authorization policy of different levels of roles
|
||||||
|
platformRole, err := o.am.GetPlatformRole(attr.GetUser().GetName())
|
||||||
|
if err != nil {
|
||||||
|
return authorizer.DecisionDeny, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check platform role policy rules
|
||||||
|
if authorized, reason, err = makeDecision(platformRole, attr); authorized == authorizer.DecisionAllow {
|
||||||
|
return authorized, reason, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's not in cluster resource, permission denied
|
||||||
|
if attr.GetCluster() == "" {
|
||||||
|
return authorizer.DecisionDeny, "permission undefined", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterRole, err := o.am.GetClusterRole(attr.GetCluster(), attr.GetUser().GetName())
|
||||||
|
if err != nil {
|
||||||
|
return authorizer.DecisionDeny, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check cluster role policy rules
|
||||||
|
if a, r, e := makeDecision(clusterRole, attr); a == authorizer.DecisionAllow {
|
||||||
|
return a, r, e
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's not in cluster resource, permission denied
|
||||||
|
if attr.GetWorkspace() == "" && attr.GetNamespace() == "" {
|
||||||
|
return authorizer.DecisionDeny, "permission undefined", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceRole, err := o.am.GetWorkspaceRole(attr.GetWorkspace(), attr.GetUser().GetName())
|
||||||
|
if err != nil {
|
||||||
|
return authorizer.DecisionDeny, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check workspace role policy rules
|
||||||
|
if a, r, e := makeDecision(workspaceRole, attr); a == authorizer.DecisionAllow {
|
||||||
|
return a, r, e
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's not in workspace resource, permission denied
|
||||||
|
if attr.GetNamespace() == "" {
|
||||||
|
return authorizer.DecisionDeny, "permission undefined", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr.GetNamespace() != "" {
|
||||||
|
namespaceRole, err := o.am.GetNamespaceRole(attr.GetCluster(), attr.GetNamespace(), attr.GetUser().GetName())
|
||||||
|
if err != nil {
|
||||||
|
return authorizer.DecisionDeny, "", err
|
||||||
|
}
|
||||||
|
// check namespace role policy rules
|
||||||
|
if a, r, e := makeDecision(namespaceRole, attr); a == authorizer.DecisionAllow {
|
||||||
|
return a, r, e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizer.DecisionDeny, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make decision base on role
|
||||||
|
func makeDecision(role am.Role, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||||
|
|
||||||
|
// Call the rego.New function to create an object that can be prepared or evaluated
|
||||||
|
// After constructing a new rego.Rego object you can call PrepareForEval() to obtain an executable query
|
||||||
|
query, err := rego.New(rego.Query("data.authz.allow"), rego.Module("authz.rego", role.GetRego())).PrepareForEval(context.Background())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return authorizer.DecisionDeny, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// data example
|
||||||
|
//{
|
||||||
|
// "User": {
|
||||||
|
// "Name": "admin",
|
||||||
|
// "UID": "0",
|
||||||
|
// "Groups": [
|
||||||
|
// "admin"
|
||||||
|
// ],
|
||||||
|
// "Extra": null
|
||||||
|
// },
|
||||||
|
// "Verb": "list",
|
||||||
|
// "Cluster": "cluster1",
|
||||||
|
// "Workspace": "",
|
||||||
|
// "Namespace": "",
|
||||||
|
// "APIGroup": "",
|
||||||
|
// "APIVersion": "v1",
|
||||||
|
// "Resource": "nodes",
|
||||||
|
// "Subresource": "",
|
||||||
|
// "Name": "",
|
||||||
|
// "KubernetesRequest": true,
|
||||||
|
// "ResourceRequest": true,
|
||||||
|
// "Path": "/api/v1/nodes"
|
||||||
|
//}
|
||||||
|
// The policy decision is contained in the results returned by the Eval() call. You can inspect the decision and handle it accordingly.
|
||||||
|
results, err := query.Eval(context.Background(), rego.EvalInput(a))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return authorizer.DecisionDeny, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) > 0 && results[0].Expressions[0].Value == true {
|
||||||
|
return authorizer.DecisionAllow, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizer.DecisionDeny, "permission undefined", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOPAAuthorizer(am am.AccessManagementInterface) *opaAuthorizer {
|
||||||
|
return &opaAuthorizer{am: am}
|
||||||
|
}
|
||||||
158
pkg/apiserver/authorization/authorizerfactory/opa_test.go
Normal file
158
pkg/apiserver/authorization/authorizerfactory/opa_test.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 authorizerfactory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
|
"kubesphere.io/kubesphere/pkg/models/iam/am"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlatformRole(t *testing.T) {
|
||||||
|
platformRoles := map[string]am.FakeRole{"admin": {
|
||||||
|
Name: "admin",
|
||||||
|
Rego: "package authz\ndefault allow = true",
|
||||||
|
}, "anonymous": {
|
||||||
|
Name: "anonymous",
|
||||||
|
Rego: "package authz\ndefault allow = false",
|
||||||
|
}, "tom": {
|
||||||
|
Name: "tom",
|
||||||
|
Rego: `package authz
|
||||||
|
default allow = false
|
||||||
|
allow {
|
||||||
|
resources_in_cluster1
|
||||||
|
}
|
||||||
|
resources_in_cluster1 {
|
||||||
|
input.Cluster == "cluster1"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
operator := am.NewFakeAMOperator()
|
||||||
|
operator.Prepare(platformRoles, nil, nil, nil)
|
||||||
|
|
||||||
|
opa := NewOPAAuthorizer(operator)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
request authorizer.AttributesRecord
|
||||||
|
expectedDecision authorizer.Decision
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "admin can list nodes",
|
||||||
|
request: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Name: "admin",
|
||||||
|
UID: "0",
|
||||||
|
Groups: []string{"admin"},
|
||||||
|
Extra: nil,
|
||||||
|
},
|
||||||
|
Verb: "list",
|
||||||
|
Cluster: "",
|
||||||
|
Workspace: "",
|
||||||
|
Namespace: "",
|
||||||
|
APIGroup: "",
|
||||||
|
APIVersion: "v1",
|
||||||
|
Resource: "nodes",
|
||||||
|
Subresource: "",
|
||||||
|
Name: "",
|
||||||
|
KubernetesRequest: true,
|
||||||
|
ResourceRequest: true,
|
||||||
|
Path: "/api/v1/nodes",
|
||||||
|
},
|
||||||
|
expectedDecision: authorizer.DecisionAllow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "anonymous can not list nodes",
|
||||||
|
request: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Name: user.Anonymous,
|
||||||
|
UID: "0",
|
||||||
|
Groups: []string{"admin"},
|
||||||
|
Extra: nil,
|
||||||
|
},
|
||||||
|
Verb: "list",
|
||||||
|
Cluster: "",
|
||||||
|
Workspace: "",
|
||||||
|
Namespace: "",
|
||||||
|
APIGroup: "",
|
||||||
|
APIVersion: "v1",
|
||||||
|
Resource: "nodes",
|
||||||
|
Subresource: "",
|
||||||
|
Name: "",
|
||||||
|
KubernetesRequest: true,
|
||||||
|
ResourceRequest: true,
|
||||||
|
Path: "/api/v1/nodes",
|
||||||
|
},
|
||||||
|
expectedDecision: authorizer.DecisionDeny,
|
||||||
|
}, {
|
||||||
|
name: "tom can list nodes in cluster1",
|
||||||
|
request: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Name: "tom",
|
||||||
|
},
|
||||||
|
Verb: "list",
|
||||||
|
Cluster: "cluster1",
|
||||||
|
Workspace: "",
|
||||||
|
Namespace: "",
|
||||||
|
APIGroup: "",
|
||||||
|
APIVersion: "v1",
|
||||||
|
Resource: "nodes",
|
||||||
|
Subresource: "",
|
||||||
|
Name: "",
|
||||||
|
KubernetesRequest: true,
|
||||||
|
ResourceRequest: true,
|
||||||
|
Path: "/api/v1/clusters/cluster1/nodes",
|
||||||
|
},
|
||||||
|
expectedDecision: authorizer.DecisionAllow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tom can not list nodes in cluster2",
|
||||||
|
request: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Name: "tom",
|
||||||
|
},
|
||||||
|
Verb: "list",
|
||||||
|
Cluster: "cluster2",
|
||||||
|
Workspace: "",
|
||||||
|
Namespace: "",
|
||||||
|
APIGroup: "",
|
||||||
|
APIVersion: "v1",
|
||||||
|
Resource: "nodes",
|
||||||
|
Subresource: "",
|
||||||
|
Name: "",
|
||||||
|
KubernetesRequest: true,
|
||||||
|
ResourceRequest: true,
|
||||||
|
Path: "/api/v1/clusters/cluster2/nodes",
|
||||||
|
},
|
||||||
|
expectedDecision: authorizer.DecisionDeny,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
decision, _, err := opa.Authorize(test.request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test failed: %s, %v", test.name, err)
|
||||||
|
}
|
||||||
|
if decision != test.expectedDecision {
|
||||||
|
t.Errorf("%s: expected decision %v, actual %+v", test.name, test.expectedDecision, decision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
pkg/apiserver/authorization/path/doc.go
Normal file
18
pkg/apiserver/authorization/path/doc.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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 path contains an authorizer that allows certain paths and path prefixes.
|
||||||
|
package path // import "k8s.io/apiserver/pkg/authorization/path"
|
||||||
67
pkg/apiserver/authorization/path/path.go
Normal file
67
pkg/apiserver/authorization/path/path.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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 path
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewAuthorizer returns an authorizer which accepts a given set of paths.
|
||||||
|
// Each path is either a fully matching path or it ends in * in case a prefix match is done. A leading / is optional.
|
||||||
|
func NewAuthorizer(alwaysAllowPaths []string) (authorizer.Authorizer, error) {
|
||||||
|
var prefixes []string
|
||||||
|
paths := sets.NewString()
|
||||||
|
for _, p := range alwaysAllowPaths {
|
||||||
|
p = strings.TrimPrefix(p, "/")
|
||||||
|
if len(p) == 0 {
|
||||||
|
// matches "/"
|
||||||
|
paths.Insert(p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.ContainsRune(p[:len(p)-1], '*') {
|
||||||
|
return nil, fmt.Errorf("only trailing * allowed in %q", p)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(p, "*") {
|
||||||
|
prefixes = append(prefixes, p[:len(p)-1])
|
||||||
|
} else {
|
||||||
|
paths.Insert(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizer.AuthorizerFunc(func(a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
if a.IsResourceRequest() {
|
||||||
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pth := strings.TrimPrefix(a.GetPath(), "/")
|
||||||
|
if paths.Has(pth) {
|
||||||
|
return authorizer.DecisionAllow, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range prefixes {
|
||||||
|
if strings.HasPrefix(pth, prefix) {
|
||||||
|
return authorizer.DecisionAllow, "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
76
pkg/apiserver/authorization/path/path_test.go
Normal file
76
pkg/apiserver/authorization/path/path_test.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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 path
|
||||||
|
|
||||||
|
import (
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewAuthorizer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
excludedPaths []string
|
||||||
|
allowed, denied, noOpinion []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"inner star", []string{"/foo*bar"}, nil, nil, nil, true},
|
||||||
|
{"double star", []string{"/foo**"}, nil, nil, nil, true},
|
||||||
|
{"empty", nil, nil, nil, []string{"/"}, false},
|
||||||
|
{"slash", []string{"/"}, []string{"/"}, nil, []string{"/foo", "//"}, false},
|
||||||
|
{"foo", []string{"/foo"}, []string{"/foo", "foo"}, nil, []string{"/", "", "/bar", "/foo/", "/fooooo", "//foo"}, false},
|
||||||
|
{"foo slash", []string{"/foo/"}, []string{"/foo/"}, nil, []string{"/", "", "/bar", "/foo", "/fooooo"}, false},
|
||||||
|
{"foo slash star", []string{"/foo/*"}, []string{"/foo/", "/foo/bar/bla"}, nil, []string{"/", "", "/foo", "/bar", "/fooooo"}, false},
|
||||||
|
{"foo bar", []string{"/foo", "/bar"}, []string{"/foo", "/bar"}, nil, []string{"/", "", "/foo/", "/bar/", "/fooooo"}, false},
|
||||||
|
{"foo star", []string{"/foo*"}, []string{"/foo", "/foooo"}, nil, []string{"/", "", "/fo", "/bar"}, false},
|
||||||
|
{"star", []string{"/*"}, []string{"/", "", "/foo", "/foooo"}, nil, nil, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
a, err := NewAuthorizer(tt.excludedPaths)
|
||||||
|
if err != nil && !tt.wantErr {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err == nil && tt.wantErr {
|
||||||
|
t.Fatalf("expected error, didn't get any")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cases := range []struct {
|
||||||
|
paths []string
|
||||||
|
want authorizer.Decision
|
||||||
|
}{
|
||||||
|
{tt.allowed, authorizer.DecisionAllow},
|
||||||
|
{tt.denied, authorizer.DecisionDeny},
|
||||||
|
{tt.noOpinion, authorizer.DecisionNoOpinion},
|
||||||
|
} {
|
||||||
|
for _, pth := range cases.paths {
|
||||||
|
info := authorizer.AttributesRecord{
|
||||||
|
Path: pth,
|
||||||
|
}
|
||||||
|
if got, _, err := a.Authorize(info); err != nil {
|
||||||
|
t.Errorf("NewAuthorizer(%v).Authorize(%q) return unexpected error: %v", tt.excludedPaths, pth, err)
|
||||||
|
} else if got != cases.want {
|
||||||
|
t.Errorf("NewAuthorizer(%v).Authorize(%q) = %v, want %v", tt.excludedPaths, pth, got, cases.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
105
pkg/apiserver/authorization/union/union.go
Normal file
105
pkg/apiserver/authorization/union/union.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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 union implements an authorizer that combines multiple subauthorizer.
|
||||||
|
// The union authorizer iterates over each subauthorizer and returns the first
|
||||||
|
// decision that is either an Allow decision or a Deny decision. If a
|
||||||
|
// subauthorizer returns a NoOpinion, then the union authorizer moves onto the
|
||||||
|
// next authorizer or, if the subauthorizer was the last authorizer, returns
|
||||||
|
// NoOpinion as the aggregate decision. I.e. union authorizer creates an
|
||||||
|
// aggregate decision and supports short-circuit allows and denies from
|
||||||
|
// subauthorizers.
|
||||||
|
package union
|
||||||
|
|
||||||
|
import (
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// unionAuthzHandler authorizer against a chain of authorizer.Authorizer
|
||||||
|
type unionAuthzHandler []authorizer.Authorizer
|
||||||
|
|
||||||
|
// New returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
|
||||||
|
func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer {
|
||||||
|
return unionAuthzHandler(authorizationHandlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful
|
||||||
|
func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
var (
|
||||||
|
errlist []error
|
||||||
|
reasonlist []string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, currAuthzHandler := range authzHandler {
|
||||||
|
decision, reason, err := currAuthzHandler.Authorize(a)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errlist = append(errlist, err)
|
||||||
|
}
|
||||||
|
if len(reason) != 0 {
|
||||||
|
reasonlist = append(reasonlist, reason)
|
||||||
|
}
|
||||||
|
switch decision {
|
||||||
|
case authorizer.DecisionAllow, authorizer.DecisionDeny:
|
||||||
|
return decision, reason, err
|
||||||
|
case authorizer.DecisionNoOpinion:
|
||||||
|
// continue to the next authorizer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver
|
||||||
|
type unionAuthzRulesHandler []authorizer.RuleResolver
|
||||||
|
|
||||||
|
// NewRuleResolvers returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
|
||||||
|
func NewRuleResolvers(authorizationHandlers ...authorizer.RuleResolver) authorizer.RuleResolver {
|
||||||
|
return unionAuthzRulesHandler(authorizationHandlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful and returns error if unsuccessful
|
||||||
|
func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||||
|
var (
|
||||||
|
errList []error
|
||||||
|
resourceRulesList []authorizer.ResourceRuleInfo
|
||||||
|
nonResourceRulesList []authorizer.NonResourceRuleInfo
|
||||||
|
)
|
||||||
|
incompleteStatus := false
|
||||||
|
|
||||||
|
for _, currAuthzHandler := range authzHandler {
|
||||||
|
resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace)
|
||||||
|
|
||||||
|
if incomplete == true {
|
||||||
|
incompleteStatus = true
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
errList = append(errList, err)
|
||||||
|
}
|
||||||
|
if len(resourceRules) > 0 {
|
||||||
|
resourceRulesList = append(resourceRulesList, resourceRules...)
|
||||||
|
}
|
||||||
|
if len(nonResourceRules) > 0 {
|
||||||
|
nonResourceRulesList = append(nonResourceRulesList, nonResourceRules...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceRulesList, nonResourceRulesList, incompleteStatus, utilerrors.NewAggregate(errList)
|
||||||
|
}
|
||||||
265
pkg/apiserver/authorization/union/union_test.go
Normal file
265
pkg/apiserver/authorization/union/union_test.go
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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 union
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockAuthzHandler struct {
|
||||||
|
decision authorizer.Decision
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *mockAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
return mock.decision, "", mock.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationSecondPasses(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
|
||||||
|
handler2 := &mockAuthzHandler{decision: authorizer.DecisionAllow}
|
||||||
|
authzHandler := New(handler1, handler2)
|
||||||
|
|
||||||
|
authorized, _, _ := authzHandler.Authorize(nil)
|
||||||
|
if authorized != authorizer.DecisionAllow {
|
||||||
|
t.Errorf("Unexpected authorization failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationFirstPasses(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzHandler{decision: authorizer.DecisionAllow}
|
||||||
|
handler2 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
|
||||||
|
authzHandler := New(handler1, handler2)
|
||||||
|
|
||||||
|
authorized, _, _ := authzHandler.Authorize(nil)
|
||||||
|
if authorized != authorizer.DecisionAllow {
|
||||||
|
t.Errorf("Unexpected authorization failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationNonePasses(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
|
||||||
|
handler2 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
|
||||||
|
authzHandler := New(handler1, handler2)
|
||||||
|
|
||||||
|
authorized, _, _ := authzHandler.Authorize(nil)
|
||||||
|
if authorized == authorizer.DecisionAllow {
|
||||||
|
t.Errorf("Expected failed authorization")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationError(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzHandler{err: fmt.Errorf("foo")}
|
||||||
|
handler2 := &mockAuthzHandler{err: fmt.Errorf("foo")}
|
||||||
|
authzHandler := New(handler1, handler2)
|
||||||
|
|
||||||
|
_, _, err := authzHandler.Authorize(nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockAuthzRuleHandler struct {
|
||||||
|
resourceRules []authorizer.ResourceRuleInfo
|
||||||
|
nonResourceRules []authorizer.NonResourceRuleInfo
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *mockAuthzRuleHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||||
|
if mock.err != nil {
|
||||||
|
return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, mock.err
|
||||||
|
}
|
||||||
|
return mock.resourceRules, mock.nonResourceRules, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationResourceRules(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzRuleHandler{
|
||||||
|
resourceRules: []authorizer.ResourceRuleInfo{
|
||||||
|
&authorizer.DefaultResourceRuleInfo{
|
||||||
|
Verbs: []string{"*"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"bindings"},
|
||||||
|
},
|
||||||
|
&authorizer.DefaultResourceRuleInfo{
|
||||||
|
Verbs: []string{"get", "list", "watch"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"*"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
handler2 := &mockAuthzRuleHandler{
|
||||||
|
resourceRules: []authorizer.ResourceRuleInfo{
|
||||||
|
&authorizer.DefaultResourceRuleInfo{
|
||||||
|
Verbs: []string{"*"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"events"},
|
||||||
|
},
|
||||||
|
&authorizer.DefaultResourceRuleInfo{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"*"},
|
||||||
|
ResourceNames: []string{"foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []authorizer.DefaultResourceRuleInfo{
|
||||||
|
{
|
||||||
|
Verbs: []string{"*"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"bindings"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Verbs: []string{"get", "list", "watch"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"*"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Verbs: []string{"*"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"events"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"*"},
|
||||||
|
ResourceNames: []string{"foo"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
authzRulesHandler := NewRuleResolvers(handler1, handler2)
|
||||||
|
|
||||||
|
rules, _, _, _ := authzRulesHandler.RulesFor(nil, "")
|
||||||
|
actual := getResourceRules(rules)
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationNonResourceRules(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzRuleHandler{
|
||||||
|
nonResourceRules: []authorizer.NonResourceRuleInfo{
|
||||||
|
&authorizer.DefaultNonResourceRuleInfo{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
NonResourceURLs: []string{"/api"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
handler2 := &mockAuthzRuleHandler{
|
||||||
|
nonResourceRules: []authorizer.NonResourceRuleInfo{
|
||||||
|
&authorizer.DefaultNonResourceRuleInfo{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
NonResourceURLs: []string{"/api/*"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []authorizer.DefaultNonResourceRuleInfo{
|
||||||
|
{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
NonResourceURLs: []string{"/api"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
NonResourceURLs: []string{"/api/*"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
authzRulesHandler := NewRuleResolvers(handler1, handler2)
|
||||||
|
|
||||||
|
_, rules, _, _ := authzRulesHandler.RulesFor(nil, "")
|
||||||
|
actual := getNonResourceRules(rules)
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizer.DefaultResourceRuleInfo {
|
||||||
|
rules := make([]authorizer.DefaultResourceRuleInfo, len(infos))
|
||||||
|
for i, info := range infos {
|
||||||
|
rules[i] = authorizer.DefaultResourceRuleInfo{
|
||||||
|
Verbs: info.GetVerbs(),
|
||||||
|
APIGroups: info.GetAPIGroups(),
|
||||||
|
Resources: info.GetResources(),
|
||||||
|
ResourceNames: info.GetResourceNames(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rules
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.DefaultNonResourceRuleInfo {
|
||||||
|
rules := make([]authorizer.DefaultNonResourceRuleInfo, len(infos))
|
||||||
|
for i, info := range infos {
|
||||||
|
rules[i] = authorizer.DefaultNonResourceRuleInfo{
|
||||||
|
Verbs: info.GetVerbs(),
|
||||||
|
NonResourceURLs: info.GetNonResourceURLs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rules
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationUnequivocalDeny(t *testing.T) {
|
||||||
|
cs := []struct {
|
||||||
|
authorizers []authorizer.Authorizer
|
||||||
|
decision authorizer.Decision
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
authorizers: []authorizer.Authorizer{},
|
||||||
|
decision: authorizer.DecisionNoOpinion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authorizers: []authorizer.Authorizer{
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionNoOpinion},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionAllow},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionDeny},
|
||||||
|
},
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authorizers: []authorizer.Authorizer{
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionNoOpinion},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionDeny},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionAllow},
|
||||||
|
},
|
||||||
|
decision: authorizer.DecisionDeny,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authorizers: []authorizer.Authorizer{
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionNoOpinion},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionDeny, err: errors.New("webhook failed closed")},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionAllow},
|
||||||
|
},
|
||||||
|
decision: authorizer.DecisionDeny,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, c := range cs {
|
||||||
|
t.Run(fmt.Sprintf("case %v", i), func(t *testing.T) {
|
||||||
|
authzHandler := New(c.authorizers...)
|
||||||
|
|
||||||
|
decision, _, _ := authzHandler.Authorize(nil)
|
||||||
|
if decision != c.decision {
|
||||||
|
t.Errorf("Unexpected authorization failure: %v, expected: %v", decision, c.decision)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
|
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
|
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||||
@@ -78,7 +78,7 @@ type Config struct {
|
|||||||
// Options below are only loaded from configuration file, no command line flags for these options now.
|
// Options below are only loaded from configuration file, no command line flags for these options now.
|
||||||
KubeSphereOptions *kubesphere.Options `json:"-" yaml:"kubesphere,omitempty" mapstructure:"kubesphere"`
|
KubeSphereOptions *kubesphere.Options `json:"-" yaml:"kubesphere,omitempty" mapstructure:"kubesphere"`
|
||||||
|
|
||||||
AuthenticateOptions *iam.AuthenticationOptions `json:"authentication,omitempty" yaml:"authenticate,omitempty" mapstructure:"authenticate"`
|
AuthenticateOptions *auth.AuthenticationOptions `json:"authentication,omitempty" yaml:"authenticate,omitempty" mapstructure:"authenticate"`
|
||||||
|
|
||||||
// Options used for enabling components, not actually used now. Once we switch Alerting/Notification API to kubesphere,
|
// Options used for enabling components, not actually used now. Once we switch Alerting/Notification API to kubesphere,
|
||||||
// we can add these options to kubesphere command lines
|
// we can add these options to kubesphere command lines
|
||||||
@@ -103,7 +103,7 @@ func New() *Config {
|
|||||||
AlertingOptions: alerting.NewAlertingOptions(),
|
AlertingOptions: alerting.NewAlertingOptions(),
|
||||||
NotificationOptions: notification.NewNotificationOptions(),
|
NotificationOptions: notification.NewNotificationOptions(),
|
||||||
LoggingOptions: elasticsearch.NewElasticSearchOptions(),
|
LoggingOptions: elasticsearch.NewElasticSearchOptions(),
|
||||||
AuthenticateOptions: iam.NewAuthenticateOptions(),
|
AuthenticateOptions: auth.NewAuthenticateOptions(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
iamapi "kubesphere.io/kubesphere/pkg/api/iam"
|
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
|
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
|
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
|
||||||
@@ -64,7 +64,7 @@ func newTestConfig() *Config {
|
|||||||
GroupSearchBase: "ou=Groups,dc=example,dc=org",
|
GroupSearchBase: "ou=Groups,dc=example,dc=org",
|
||||||
},
|
},
|
||||||
RedisOptions: &cache.Options{
|
RedisOptions: &cache.Options{
|
||||||
Host: "localhost:6379",
|
Host: "localhost",
|
||||||
Port: 6379,
|
Port: 6379,
|
||||||
Password: "P@88w0rd",
|
Password: "P@88w0rd",
|
||||||
DB: 0,
|
DB: 0,
|
||||||
@@ -106,7 +106,7 @@ func newTestConfig() *Config {
|
|||||||
NotificationOptions: ¬ification.Options{
|
NotificationOptions: ¬ification.Options{
|
||||||
Endpoint: "http://notification.kubesphere-alerting-system.svc:9200",
|
Endpoint: "http://notification.kubesphere-alerting-system.svc:9200",
|
||||||
},
|
},
|
||||||
AuthenticateOptions: &iamapi.AuthenticationOptions{
|
AuthenticateOptions: &auth.AuthenticationOptions{
|
||||||
AuthenticateRateLimiterMaxTries: 5,
|
AuthenticateRateLimiterMaxTries: 5,
|
||||||
AuthenticateRateLimiterDuration: 30 * time.Minute,
|
AuthenticateRateLimiterDuration: 30 * time.Minute,
|
||||||
MaxAuthenticateRetries: 6,
|
MaxAuthenticateRetries: 6,
|
||||||
|
|||||||
@@ -1,36 +1,76 @@
|
|||||||
package dispatch
|
package dispatch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
"fmt"
|
||||||
"net/http"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/util/proxy"
|
"k8s.io/apimachinery/pkg/util/proxy"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
|
towerv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||||
|
"kubesphere.io/kubesphere/pkg/client/listers/tower/v1alpha1"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultMultipleClusterAgentNamespace = "kubesphere-system"
|
||||||
|
|
||||||
// Dispatcher defines how to forward request to designated cluster based on cluster name
|
// Dispatcher defines how to forward request to designated cluster based on cluster name
|
||||||
type Dispatcher interface {
|
type Dispatcher interface {
|
||||||
Dispatch(w http.ResponseWriter, req *http.Request)
|
Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultClusterDispatch = newClusterDispatch()
|
|
||||||
|
|
||||||
type clusterDispatch struct {
|
type clusterDispatch struct {
|
||||||
transport *http.Transport
|
agentLister v1alpha1.AgentLister
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClusterDispatch() Dispatcher {
|
func NewClusterDispatch(agentLister v1alpha1.AgentLister) Dispatcher {
|
||||||
return &clusterDispatch{}
|
return &clusterDispatch{
|
||||||
|
agentLister: agentLister,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clusterDispatch) Dispatch(w http.ResponseWriter, req *http.Request) {
|
func (c *clusterDispatch) Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler) {
|
||||||
|
|
||||||
|
info, _ := request.RequestInfoFrom(req.Context())
|
||||||
|
if info.Cluster == "" { // fallback to host cluster if cluster name if empty
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
agent, err := c.agentLister.Agents(defaultMultipleClusterAgentNamespace).Get(info.Cluster)
|
||||||
|
if err != nil {
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
http.Error(w, fmt.Sprintf("cluster %s not found", info.Cluster), http.StatusNotFound)
|
||||||
|
} else {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isAgentReady(agent) {
|
||||||
|
http.Error(w, fmt.Sprintf("cluster agent is not ready"), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
u := *req.URL
|
u := *req.URL
|
||||||
// u.Host = someHost
|
u.Host = agent.Spec.Proxy
|
||||||
|
u.Path = strings.Replace(u.Path, fmt.Sprintf("/clusters/%s", info.Cluster), "", 1)
|
||||||
|
|
||||||
httpProxy := proxy.NewUpgradeAwareHandler(&u, c.transport, false, false, c)
|
httpProxy := proxy.NewUpgradeAwareHandler(&u, http.DefaultTransport, true, false, c)
|
||||||
httpProxy.ServeHTTP(w, req)
|
httpProxy.ServeHTTP(w, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clusterDispatch) Error(w http.ResponseWriter, req *http.Request, err error) {
|
func (c *clusterDispatch) Error(w http.ResponseWriter, req *http.Request, err error) {
|
||||||
responsewriters.InternalError(w, req, err)
|
responsewriters.InternalError(w, req, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAgentReady(agent *towerv1alpha1.Agent) bool {
|
||||||
|
for _, condition := range agent.Status.Conditions {
|
||||||
|
if condition.Type == towerv1alpha1.AgentConnected && condition.Status == corev1.ConditionTrue {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,27 +1,44 @@
|
|||||||
package filters
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithAuthentication installs authentication handler to handler chain.
|
// WithAuthentication installs authentication handler to handler chain.
|
||||||
func WithAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler) http.Handler {
|
func WithAuthentication(handler http.Handler, auth authenticator.Request) http.Handler {
|
||||||
if auth == nil {
|
if auth == nil {
|
||||||
klog.Warningf("Authentication is disabled")
|
klog.Warningf("Authentication is disabled")
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
//authenticationStart := time.Now()
|
|
||||||
|
|
||||||
resp, ok, err := auth.AuthenticateRequest(req)
|
resp, ok, err := auth.AuthenticateRequest(req)
|
||||||
if err != nil || !ok {
|
if err != nil || !ok {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Unable to authenticate the request due to error: %v", err)
|
klog.Errorf("Unable to authenticate the request due to error: %v", err)
|
||||||
}
|
}
|
||||||
failed.ServeHTTP(w, req)
|
|
||||||
|
ctx := req.Context()
|
||||||
|
requestInfo, found := request.RequestInfoFrom(ctx)
|
||||||
|
if !found {
|
||||||
|
responsewriters.InternalError(w, req, errors.New("no RequestInfo found in the context"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
|
||||||
|
responsewriters.ErrorNegotiated(apierrors.NewUnauthorized("Unauthorized"), s, gv, w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package filters
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
k8srequest "k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@@ -18,6 +19,8 @@ func WithAuthorization(handler http.Handler, a authorizer.Authorizer) http.Handl
|
|||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serializer := 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) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
|
|
||||||
@@ -38,14 +41,14 @@ func WithAuthorization(handler http.Handler, a authorizer.Authorizer) http.Handl
|
|||||||
}
|
}
|
||||||
|
|
||||||
klog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason)
|
klog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason)
|
||||||
w.WriteHeader(http.StatusForbidden)
|
responsewriters.Forbidden(ctx, attributes, w, req, reason, serializer)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error) {
|
func GetAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error) {
|
||||||
attribs := authorizer.AttributesRecord{}
|
attribs := authorizer.AttributesRecord{}
|
||||||
|
|
||||||
user, ok := k8srequest.UserFrom(ctx)
|
user, ok := request.UserFrom(ctx)
|
||||||
if ok {
|
if ok {
|
||||||
attribs.User = user
|
attribs.User = user
|
||||||
}
|
}
|
||||||
@@ -59,6 +62,9 @@ func GetAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error)
|
|||||||
attribs.ResourceRequest = requestInfo.IsResourceRequest
|
attribs.ResourceRequest = requestInfo.IsResourceRequest
|
||||||
attribs.Path = requestInfo.Path
|
attribs.Path = requestInfo.Path
|
||||||
attribs.Verb = requestInfo.Verb
|
attribs.Verb = requestInfo.Verb
|
||||||
|
attribs.Cluster = requestInfo.Cluster
|
||||||
|
attribs.Workspace = requestInfo.Workspace
|
||||||
|
attribs.KubernetesRequest = requestInfo.IsKubernetesRequest
|
||||||
|
|
||||||
attribs.APIGroup = requestInfo.APIGroup
|
attribs.APIGroup = requestInfo.APIGroup
|
||||||
attribs.APIVersion = requestInfo.APIVersion
|
attribs.APIVersion = requestInfo.APIVersion
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func WithMultipleClusterDispatcher(handler http.Handler, dispatch dispatch.Dispa
|
|||||||
if info.Cluster == "" {
|
if info.Cluster == "" {
|
||||||
handler.ServeHTTP(w, req)
|
handler.ServeHTTP(w, req)
|
||||||
} else {
|
} else {
|
||||||
dispatch.Dispatch(w, req)
|
dispatch.Dispatch(w, req, handler)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package filters
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
@@ -8,6 +9,7 @@ import (
|
|||||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/proxy"
|
"k8s.io/apimachinery/pkg/util/proxy"
|
||||||
)
|
)
|
||||||
@@ -33,6 +35,8 @@ func WithKubeAPIServer(handler http.Handler, config *rest.Config, failed proxy.E
|
|||||||
s := *req.URL
|
s := *req.URL
|
||||||
s.Host = kubernetes.Host
|
s.Host = kubernetes.Host
|
||||||
s.Scheme = kubernetes.Scheme
|
s.Scheme = kubernetes.Scheme
|
||||||
|
// remove cluster path
|
||||||
|
s.Path = strings.Replace(s.Path, fmt.Sprintf("/clusters/%s", info.Cluster), "", 1)
|
||||||
|
|
||||||
httpProxy := proxy.NewUpgradeAwareHandler(&s, defaultTransport, true, false, failed)
|
httpProxy := proxy.NewUpgradeAwareHandler(&s, defaultTransport, true, false, failed)
|
||||||
httpProxy.ServeHTTP(w, req)
|
httpProxy.ServeHTTP(w, req)
|
||||||
|
|||||||
96
pkg/apiserver/request/context.go
Normal file
96
pkg/apiserver/request/context.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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 request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The key type is unexported to prevent collisions
|
||||||
|
type key int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// namespaceKey is the context key for the request namespace.
|
||||||
|
namespaceKey key = iota
|
||||||
|
|
||||||
|
// userKey is the context key for the request user.
|
||||||
|
userKey
|
||||||
|
|
||||||
|
// auditKey is the context key for the audit event.
|
||||||
|
auditKey
|
||||||
|
|
||||||
|
// audiencesKey is the context key for request audiences.
|
||||||
|
audiencesKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewContext instantiates a base context object for request flows.
|
||||||
|
func NewContext() context.Context {
|
||||||
|
return context.TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultContext instantiates a base context object for request flows in the default namespace
|
||||||
|
func NewDefaultContext() context.Context {
|
||||||
|
return WithNamespace(NewContext(), metav1.NamespaceDefault)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValue returns a copy of parent in which the value associated with key is val.
|
||||||
|
func WithValue(parent context.Context, key interface{}, val interface{}) context.Context {
|
||||||
|
return context.WithValue(parent, key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNamespace returns a copy of parent in which the namespace value is set
|
||||||
|
func WithNamespace(parent context.Context, namespace string) context.Context {
|
||||||
|
return WithValue(parent, namespaceKey, namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceFrom returns the value of the namespace key on the ctx
|
||||||
|
func NamespaceFrom(ctx context.Context) (string, bool) {
|
||||||
|
namespace, ok := ctx.Value(namespaceKey).(string)
|
||||||
|
return namespace, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceValue returns the value of the namespace key on the ctx, or the empty string if none
|
||||||
|
func NamespaceValue(ctx context.Context) string {
|
||||||
|
namespace, _ := NamespaceFrom(ctx)
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUser returns a copy of parent in which the user value is set
|
||||||
|
func WithUser(parent context.Context, user user.Info) context.Context {
|
||||||
|
return WithValue(parent, userKey, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserFrom returns the value of the user key on the ctx
|
||||||
|
func UserFrom(ctx context.Context) (user.Info, bool) {
|
||||||
|
user, ok := ctx.Value(userKey).(user.Info)
|
||||||
|
return user, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAuditEvent returns set audit event struct.
|
||||||
|
func WithAuditEvent(parent context.Context, ev *audit.Event) context.Context {
|
||||||
|
return WithValue(parent, auditKey, ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuditEventFrom returns the audit event struct on the ctx
|
||||||
|
func AuditEventFrom(ctx context.Context) *audit.Event {
|
||||||
|
ev, _ := ctx.Value(auditKey).(*audit.Event)
|
||||||
|
return ev
|
||||||
|
}
|
||||||
93
pkg/apiserver/request/context_test.go
Normal file
93
pkg/apiserver/request/context_test.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
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 request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestNamespaceContext validates that a namespace can be get/set on a context object
|
||||||
|
func TestNamespaceContext(t *testing.T) {
|
||||||
|
ctx := NewDefaultContext()
|
||||||
|
result, ok := NamespaceFrom(ctx)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Error getting namespace")
|
||||||
|
}
|
||||||
|
if metav1.NamespaceDefault != result {
|
||||||
|
t.Fatalf("Expected: %s, Actual: %s", metav1.NamespaceDefault, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = NewContext()
|
||||||
|
result, ok = NamespaceFrom(ctx)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("Should not be ok because there is no namespace on the context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TestUserContext validates that a userinfo can be get/set on a context object
|
||||||
|
func TestUserContext(t *testing.T) {
|
||||||
|
ctx := NewContext()
|
||||||
|
_, ok := UserFrom(ctx)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("Should not be ok because there is no user.Info on the context")
|
||||||
|
}
|
||||||
|
ctx = WithUser(
|
||||||
|
ctx,
|
||||||
|
&user.DefaultInfo{
|
||||||
|
Name: "bob",
|
||||||
|
UID: "123",
|
||||||
|
Groups: []string{"group1"},
|
||||||
|
Extra: map[string][]string{"foo": {"bar"}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
result, ok := UserFrom(ctx)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Error getting user info")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedName := "bob"
|
||||||
|
if result.GetName() != expectedName {
|
||||||
|
t.Fatalf("Get user name error, Expected: %s, Actual: %s", expectedName, result.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedUID := "123"
|
||||||
|
if result.GetUID() != expectedUID {
|
||||||
|
t.Fatalf("Get UID error, Expected: %s, Actual: %s", expectedUID, result.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedGroup := "group1"
|
||||||
|
actualGroup := result.GetGroups()
|
||||||
|
if len(actualGroup) != 1 {
|
||||||
|
t.Fatalf("Get user group number error, Expected: 1, Actual: %d", len(actualGroup))
|
||||||
|
} else if actualGroup[0] != expectedGroup {
|
||||||
|
t.Fatalf("Get user group error, Expected: %s, Actual: %s", expectedGroup, actualGroup[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedExtraKey := "foo"
|
||||||
|
expectedExtraValue := "bar"
|
||||||
|
actualExtra := result.GetExtra()
|
||||||
|
if len(actualExtra[expectedExtraKey]) != 1 {
|
||||||
|
t.Fatalf("Get user extra map number error, Expected: 1, Actual: %d", len(actualExtra[expectedExtraKey]))
|
||||||
|
} else if actualExtra[expectedExtraKey][0] != expectedExtraValue {
|
||||||
|
t.Fatalf("Get user extra map value error, Expected: %s, Actual: %s", expectedExtraValue, actualExtra[expectedExtraKey])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,7 +3,11 @@ package request
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"k8s.io/apimachinery/pkg/api/validation/path"
|
||||||
|
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/klog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -19,6 +23,13 @@ type RequestInfoResolver interface {
|
|||||||
// master's Mux.
|
// master's Mux.
|
||||||
var specialVerbs = sets.NewString("proxy", "watch")
|
var specialVerbs = sets.NewString("proxy", "watch")
|
||||||
|
|
||||||
|
// specialVerbsNoSubresources contains root verbs which do not allow subresources
|
||||||
|
var specialVerbsNoSubresources = sets.NewString("proxy")
|
||||||
|
|
||||||
|
// namespaceSubresources contains subresources of namespace
|
||||||
|
// this list allows the parser to distinguish between a namespace subresource, and a namespaced resource
|
||||||
|
var namespaceSubresources = sets.NewString("status", "finalize")
|
||||||
|
|
||||||
var kubernetesAPIPrefixes = sets.NewString("api", "apis")
|
var kubernetesAPIPrefixes = sets.NewString("api", "apis")
|
||||||
|
|
||||||
// RequestInfo holds information parsed from the http.Request,
|
// RequestInfo holds information parsed from the http.Request,
|
||||||
@@ -26,10 +37,10 @@ var kubernetesAPIPrefixes = sets.NewString("api", "apis")
|
|||||||
type RequestInfo struct {
|
type RequestInfo struct {
|
||||||
*k8srequest.RequestInfo
|
*k8srequest.RequestInfo
|
||||||
|
|
||||||
// IsKubeSphereRequest indicates whether or not the request should be handled by kubernetes or kubesphere
|
// IsKubernetesRequest indicates whether or not the request should be handled by kubernetes or kubesphere
|
||||||
IsKubernetesRequest bool
|
IsKubernetesRequest bool
|
||||||
|
|
||||||
// Workspace of requested namespace, for non-workspaced resources, this may be empty
|
// Workspace of requested resource, for non-workspaced resources, this may be empty
|
||||||
Workspace string
|
Workspace string
|
||||||
|
|
||||||
// Cluster of requested resource, this is empty in single-cluster environment
|
// Cluster of requested resource, this is empty in single-cluster environment
|
||||||
@@ -37,9 +48,8 @@ type RequestInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RequestInfoFactory struct {
|
type RequestInfoFactory struct {
|
||||||
APIPrefixes sets.String
|
APIPrefixes sets.String
|
||||||
GrouplessAPIPrefixes sets.String
|
GrouplessAPIPrefixes sets.String
|
||||||
k8sRequestInfoFactory *k8srequest.RequestInfoFactory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure
|
// NewRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure
|
||||||
@@ -99,16 +109,9 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
|||||||
currentParts = currentParts[1:]
|
currentParts = currentParts[1:]
|
||||||
|
|
||||||
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
|
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
|
||||||
if len(currentParts) < 2 {
|
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
|
||||||
return &requestInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentParts[0] == "clusters" {
|
|
||||||
requestInfo.Cluster = currentParts[1]
|
|
||||||
currentParts = currentParts[2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(currentParts) < 3 {
|
if len(currentParts) < 3 {
|
||||||
|
// return a non-resource request
|
||||||
return &requestInfo, nil
|
return &requestInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +123,16 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
|||||||
requestInfo.APIVersion = currentParts[0]
|
requestInfo.APIVersion = currentParts[0]
|
||||||
currentParts = currentParts[1:]
|
currentParts = currentParts[1:]
|
||||||
|
|
||||||
|
if currentParts[0] == "clusters" {
|
||||||
|
requestInfo.Cluster = currentParts[1]
|
||||||
|
currentParts = currentParts[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentParts[0] == "workspaces" {
|
||||||
|
requestInfo.Workspace = currentParts[1]
|
||||||
|
currentParts = currentParts[2:]
|
||||||
|
}
|
||||||
|
|
||||||
if specialVerbs.Has(currentParts[0]) {
|
if specialVerbs.Has(currentParts[0]) {
|
||||||
if len(currentParts) < 2 {
|
if len(currentParts) < 2 {
|
||||||
return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url: %v", req.URL)
|
return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url: %v", req.URL)
|
||||||
@@ -144,6 +157,73 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
||||||
|
if currentParts[0] == "namespaces" {
|
||||||
|
if len(currentParts) > 1 {
|
||||||
|
requestInfo.Namespace = currentParts[1]
|
||||||
|
|
||||||
|
// if there is another step after the namespace name and it is not a known namespace subresource
|
||||||
|
// move currentParts to include it as a resource in its own right
|
||||||
|
if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) {
|
||||||
|
currentParts = currentParts[2:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestInfo.Namespace = metav1.NamespaceNone
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsing successful, so we now know the proper value for .Parts
|
||||||
|
requestInfo.Parts = currentParts
|
||||||
|
|
||||||
|
// parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
|
||||||
|
switch {
|
||||||
|
case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb):
|
||||||
|
requestInfo.Subresource = requestInfo.Parts[2]
|
||||||
|
fallthrough
|
||||||
|
case len(requestInfo.Parts) >= 2:
|
||||||
|
requestInfo.Name = requestInfo.Parts[1]
|
||||||
|
fallthrough
|
||||||
|
case len(requestInfo.Parts) >= 1:
|
||||||
|
requestInfo.Resource = requestInfo.Parts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there's no name on the request and we thought it was a get before, then the actual verb is a list or a watch
|
||||||
|
if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
|
||||||
|
opts := metainternalversion.ListOptions{}
|
||||||
|
if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, &opts); err != nil {
|
||||||
|
// An error in parsing request will result in default to "list" and not setting "name" field.
|
||||||
|
klog.Errorf("Couldn't parse request %#v: %v", req.URL.Query(), err)
|
||||||
|
// Reset opts to not rely on partial results from parsing.
|
||||||
|
// However, if watch is set, let's report it.
|
||||||
|
opts = metainternalversion.ListOptions{}
|
||||||
|
if values := req.URL.Query()["watch"]; len(values) > 0 {
|
||||||
|
switch strings.ToLower(values[0]) {
|
||||||
|
case "false", "0":
|
||||||
|
default:
|
||||||
|
opts.Watch = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Watch {
|
||||||
|
requestInfo.Verb = "watch"
|
||||||
|
} else {
|
||||||
|
requestInfo.Verb = "list"
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.FieldSelector != nil {
|
||||||
|
if name, ok := opts.FieldSelector.RequiresExactMatch("metadata.name"); ok {
|
||||||
|
if len(path.IsValidPathSegmentName(name)) == 0 {
|
||||||
|
requestInfo.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection
|
||||||
|
if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" {
|
||||||
|
requestInfo.Verb = "deletecollection"
|
||||||
|
}
|
||||||
|
|
||||||
return &requestInfo, nil
|
return &requestInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
218
pkg/apiserver/request/requestinfo_test.go
Normal file
218
pkg/apiserver/request/requestinfo_test.go
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestRequestInfoResolver() RequestInfoResolver {
|
||||||
|
requestInfoResolver := &RequestInfoFactory{
|
||||||
|
APIPrefixes: sets.NewString("api", "apis", "kapis", "kapi"),
|
||||||
|
GrouplessAPIPrefixes: sets.NewString("api", "kapi"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestInfoResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
method string
|
||||||
|
expectedErr error
|
||||||
|
expectedVerb string
|
||||||
|
expectedResource string
|
||||||
|
expectedIsResourceRequest bool
|
||||||
|
expectedCluster string
|
||||||
|
expectedWorkspace string
|
||||||
|
expectedNamespace string
|
||||||
|
expectedKubernetesRequest bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "login",
|
||||||
|
url: "/oauth/authorize?client_id=ks-console&response_type=token",
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "POST",
|
||||||
|
expectedResource: "",
|
||||||
|
expectedIsResourceRequest: false,
|
||||||
|
expectedCluster: "",
|
||||||
|
expectedKubernetesRequest: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list cluster roles",
|
||||||
|
url: "/apis/rbac.authorization.k8s.io/v1/clusters/cluster1/clusterroles",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "list",
|
||||||
|
expectedResource: "clusterroles",
|
||||||
|
expectedIsResourceRequest: true,
|
||||||
|
expectedCluster: "cluster1",
|
||||||
|
expectedKubernetesRequest: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list cluster nodes",
|
||||||
|
url: "/api/v1/clusters/cluster1/nodes",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "list",
|
||||||
|
expectedResource: "nodes",
|
||||||
|
expectedIsResourceRequest: true,
|
||||||
|
expectedCluster: "cluster1",
|
||||||
|
expectedKubernetesRequest: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list cluster nodes",
|
||||||
|
url: "/api/v1/clusters/cluster1/nodes",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "list",
|
||||||
|
expectedResource: "nodes",
|
||||||
|
expectedIsResourceRequest: true,
|
||||||
|
expectedCluster: "cluster1",
|
||||||
|
expectedKubernetesRequest: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list cluster nodes",
|
||||||
|
url: "/api/v1/nodes",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "list",
|
||||||
|
expectedResource: "nodes",
|
||||||
|
expectedIsResourceRequest: true,
|
||||||
|
expectedCluster: "",
|
||||||
|
expectedKubernetesRequest: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list roles",
|
||||||
|
url: "/apis/rbac.authorization.k8s.io/v1/clusters/cluster1/namespaces/namespace1/roles",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "list",
|
||||||
|
expectedResource: "roles",
|
||||||
|
expectedIsResourceRequest: true,
|
||||||
|
expectedNamespace: "namespace1",
|
||||||
|
expectedCluster: "cluster1",
|
||||||
|
expectedKubernetesRequest: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list roles",
|
||||||
|
url: "/apis/rbac.authorization.k8s.io/v1/namespaces/namespace1/roles",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "list",
|
||||||
|
expectedResource: "roles",
|
||||||
|
expectedIsResourceRequest: true,
|
||||||
|
expectedCluster: "",
|
||||||
|
expectedNamespace: "namespace1",
|
||||||
|
expectedKubernetesRequest: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list namespaces",
|
||||||
|
url: "/kapis/resources.kubesphere.io/v1alpha3/workspaces/workspace1/namespaces",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "list",
|
||||||
|
expectedResource: "namespaces",
|
||||||
|
expectedIsResourceRequest: true,
|
||||||
|
expectedWorkspace: "workspace1",
|
||||||
|
expectedCluster: "",
|
||||||
|
expectedKubernetesRequest: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list namespaces",
|
||||||
|
url: "/kapis/resources.kubesphere.io/v1alpha3/clusters/cluster1/workspaces/workspace1/namespaces",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "list",
|
||||||
|
expectedResource: "namespaces",
|
||||||
|
expectedIsResourceRequest: true,
|
||||||
|
expectedWorkspace: "workspace1",
|
||||||
|
expectedCluster: "cluster1",
|
||||||
|
expectedKubernetesRequest: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "random query",
|
||||||
|
url: "/foo/bar",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "GET",
|
||||||
|
expectedResource: "",
|
||||||
|
expectedIsResourceRequest: false,
|
||||||
|
expectedWorkspace: "",
|
||||||
|
expectedCluster: "",
|
||||||
|
expectedKubernetesRequest: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "kubesphere api without clusters",
|
||||||
|
url: "/kapis/foo/bar/",
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "POST",
|
||||||
|
expectedResource: "",
|
||||||
|
expectedNamespace: "",
|
||||||
|
expectedWorkspace: "",
|
||||||
|
expectedCluster: "",
|
||||||
|
expectedIsResourceRequest: false,
|
||||||
|
expectedKubernetesRequest: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
requestInfoResolver := newTestRequestInfoResolver()
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
req, err := http.NewRequest(test.method, test.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
requestInfo, err := requestInfoResolver.NewRequestInfo(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if test.expectedErr != err {
|
||||||
|
t.Errorf("%s: expected error %v, actual %v", test.name, test.expectedErr, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if test.expectedVerb != requestInfo.Verb {
|
||||||
|
t.Errorf("%s: expected verb %v, actual %+v", test.name, test.expectedVerb, requestInfo.Verb)
|
||||||
|
}
|
||||||
|
if test.expectedResource != requestInfo.Resource {
|
||||||
|
t.Errorf("%s: expected resource %v, actual %+v", test.name, test.expectedResource, requestInfo.Resource)
|
||||||
|
}
|
||||||
|
if test.expectedIsResourceRequest != requestInfo.IsResourceRequest {
|
||||||
|
t.Errorf("%s: expected is resource request %v, actual %+v", test.name, test.expectedIsResourceRequest, requestInfo.IsResourceRequest)
|
||||||
|
}
|
||||||
|
if test.expectedCluster != requestInfo.Cluster {
|
||||||
|
t.Errorf("%s: expected cluster %v, actual %+v", test.name, test.expectedCluster, requestInfo.Cluster)
|
||||||
|
}
|
||||||
|
if test.expectedWorkspace != requestInfo.Workspace {
|
||||||
|
t.Errorf("%s: expected workspace %v, actual %+v", test.name, test.expectedWorkspace, requestInfo.Workspace)
|
||||||
|
}
|
||||||
|
if test.expectedNamespace != requestInfo.Namespace {
|
||||||
|
t.Errorf("%s: expected namespace %v, actual %+v", test.name, test.expectedNamespace, requestInfo.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.expectedKubernetesRequest != requestInfo.IsKubernetesRequest {
|
||||||
|
t.Errorf("%s: expected kubernetes request %v, actual %+v", test.name, test.expectedKubernetesRequest, requestInfo.IsKubernetesRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
networkv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/network/v1alpha1"
|
networkv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/network/v1alpha1"
|
||||||
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/servicemesh/v1alpha2"
|
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/servicemesh/v1alpha2"
|
||||||
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tenant/v1alpha1"
|
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tenant/v1alpha1"
|
||||||
|
towerv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tower/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
@@ -38,6 +39,7 @@ type Interface interface {
|
|||||||
NetworkV1alpha1() networkv1alpha1.NetworkV1alpha1Interface
|
NetworkV1alpha1() networkv1alpha1.NetworkV1alpha1Interface
|
||||||
ServicemeshV1alpha2() servicemeshv1alpha2.ServicemeshV1alpha2Interface
|
ServicemeshV1alpha2() servicemeshv1alpha2.ServicemeshV1alpha2Interface
|
||||||
TenantV1alpha1() tenantv1alpha1.TenantV1alpha1Interface
|
TenantV1alpha1() tenantv1alpha1.TenantV1alpha1Interface
|
||||||
|
TowerV1alpha1() towerv1alpha1.TowerV1alpha1Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clientset contains the clients for groups. Each group has exactly one
|
// Clientset contains the clients for groups. Each group has exactly one
|
||||||
@@ -49,6 +51,7 @@ type Clientset struct {
|
|||||||
networkV1alpha1 *networkv1alpha1.NetworkV1alpha1Client
|
networkV1alpha1 *networkv1alpha1.NetworkV1alpha1Client
|
||||||
servicemeshV1alpha2 *servicemeshv1alpha2.ServicemeshV1alpha2Client
|
servicemeshV1alpha2 *servicemeshv1alpha2.ServicemeshV1alpha2Client
|
||||||
tenantV1alpha1 *tenantv1alpha1.TenantV1alpha1Client
|
tenantV1alpha1 *tenantv1alpha1.TenantV1alpha1Client
|
||||||
|
towerV1alpha1 *towerv1alpha1.TowerV1alpha1Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// DevopsV1alpha1 retrieves the DevopsV1alpha1Client
|
// DevopsV1alpha1 retrieves the DevopsV1alpha1Client
|
||||||
@@ -76,6 +79,11 @@ func (c *Clientset) TenantV1alpha1() tenantv1alpha1.TenantV1alpha1Interface {
|
|||||||
return c.tenantV1alpha1
|
return c.tenantV1alpha1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TowerV1alpha1 retrieves the TowerV1alpha1Client
|
||||||
|
func (c *Clientset) TowerV1alpha1() towerv1alpha1.TowerV1alpha1Interface {
|
||||||
|
return c.towerV1alpha1
|
||||||
|
}
|
||||||
|
|
||||||
// Discovery retrieves the DiscoveryClient
|
// Discovery retrieves the DiscoveryClient
|
||||||
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
@@ -117,6 +125,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
cs.towerV1alpha1, err = towerv1alpha1.NewForConfig(&configShallowCopy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
|
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -134,6 +146,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
|
|||||||
cs.networkV1alpha1 = networkv1alpha1.NewForConfigOrDie(c)
|
cs.networkV1alpha1 = networkv1alpha1.NewForConfigOrDie(c)
|
||||||
cs.servicemeshV1alpha2 = servicemeshv1alpha2.NewForConfigOrDie(c)
|
cs.servicemeshV1alpha2 = servicemeshv1alpha2.NewForConfigOrDie(c)
|
||||||
cs.tenantV1alpha1 = tenantv1alpha1.NewForConfigOrDie(c)
|
cs.tenantV1alpha1 = tenantv1alpha1.NewForConfigOrDie(c)
|
||||||
|
cs.towerV1alpha1 = towerv1alpha1.NewForConfigOrDie(c)
|
||||||
|
|
||||||
cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
|
cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
|
||||||
return &cs
|
return &cs
|
||||||
@@ -147,6 +160,7 @@ func New(c rest.Interface) *Clientset {
|
|||||||
cs.networkV1alpha1 = networkv1alpha1.New(c)
|
cs.networkV1alpha1 = networkv1alpha1.New(c)
|
||||||
cs.servicemeshV1alpha2 = servicemeshv1alpha2.New(c)
|
cs.servicemeshV1alpha2 = servicemeshv1alpha2.New(c)
|
||||||
cs.tenantV1alpha1 = tenantv1alpha1.New(c)
|
cs.tenantV1alpha1 = tenantv1alpha1.New(c)
|
||||||
|
cs.towerV1alpha1 = towerv1alpha1.New(c)
|
||||||
|
|
||||||
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
|
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
|
||||||
return &cs
|
return &cs
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import (
|
|||||||
fakeservicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/servicemesh/v1alpha2/fake"
|
fakeservicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/servicemesh/v1alpha2/fake"
|
||||||
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tenant/v1alpha1"
|
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tenant/v1alpha1"
|
||||||
faketenantv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tenant/v1alpha1/fake"
|
faketenantv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tenant/v1alpha1/fake"
|
||||||
|
towerv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tower/v1alpha1"
|
||||||
|
faketowerv1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tower/v1alpha1/fake"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSimpleClientset returns a clientset that will respond with the provided objects.
|
// NewSimpleClientset returns a clientset that will respond with the provided objects.
|
||||||
@@ -108,3 +110,8 @@ func (c *Clientset) ServicemeshV1alpha2() servicemeshv1alpha2.ServicemeshV1alpha
|
|||||||
func (c *Clientset) TenantV1alpha1() tenantv1alpha1.TenantV1alpha1Interface {
|
func (c *Clientset) TenantV1alpha1() tenantv1alpha1.TenantV1alpha1Interface {
|
||||||
return &faketenantv1alpha1.FakeTenantV1alpha1{Fake: &c.Fake}
|
return &faketenantv1alpha1.FakeTenantV1alpha1{Fake: &c.Fake}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TowerV1alpha1 retrieves the TowerV1alpha1Client
|
||||||
|
func (c *Clientset) TowerV1alpha1() towerv1alpha1.TowerV1alpha1Interface {
|
||||||
|
return &faketowerv1alpha1.FakeTowerV1alpha1{Fake: &c.Fake}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1"
|
networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1"
|
||||||
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2"
|
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2"
|
||||||
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
||||||
|
towerv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var scheme = runtime.NewScheme()
|
var scheme = runtime.NewScheme()
|
||||||
@@ -40,6 +41,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
|||||||
networkv1alpha1.AddToScheme,
|
networkv1alpha1.AddToScheme,
|
||||||
servicemeshv1alpha2.AddToScheme,
|
servicemeshv1alpha2.AddToScheme,
|
||||||
tenantv1alpha1.AddToScheme,
|
tenantv1alpha1.AddToScheme,
|
||||||
|
towerv1alpha1.AddToScheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1"
|
networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1"
|
||||||
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2"
|
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2"
|
||||||
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
||||||
|
towerv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Scheme = runtime.NewScheme()
|
var Scheme = runtime.NewScheme()
|
||||||
@@ -40,6 +41,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
|||||||
networkv1alpha1.AddToScheme,
|
networkv1alpha1.AddToScheme,
|
||||||
servicemeshv1alpha2.AddToScheme,
|
servicemeshv1alpha2.AddToScheme,
|
||||||
tenantv1alpha1.AddToScheme,
|
tenantv1alpha1.AddToScheme,
|
||||||
|
towerv1alpha1.AddToScheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||||
|
|||||||
191
pkg/client/clientset/versioned/typed/tower/v1alpha1/agent.go
Normal file
191
pkg/client/clientset/versioned/typed/tower/v1alpha1/agent.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by client-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
watch "k8s.io/apimachinery/pkg/watch"
|
||||||
|
rest "k8s.io/client-go/rest"
|
||||||
|
v1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||||
|
scheme "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AgentsGetter has a method to return a AgentInterface.
|
||||||
|
// A group's client should implement this interface.
|
||||||
|
type AgentsGetter interface {
|
||||||
|
Agents(namespace string) AgentInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentInterface has methods to work with Agent resources.
|
||||||
|
type AgentInterface interface {
|
||||||
|
Create(*v1alpha1.Agent) (*v1alpha1.Agent, error)
|
||||||
|
Update(*v1alpha1.Agent) (*v1alpha1.Agent, error)
|
||||||
|
UpdateStatus(*v1alpha1.Agent) (*v1alpha1.Agent, error)
|
||||||
|
Delete(name string, options *v1.DeleteOptions) error
|
||||||
|
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
|
||||||
|
Get(name string, options v1.GetOptions) (*v1alpha1.Agent, error)
|
||||||
|
List(opts v1.ListOptions) (*v1alpha1.AgentList, error)
|
||||||
|
Watch(opts v1.ListOptions) (watch.Interface, error)
|
||||||
|
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Agent, err error)
|
||||||
|
AgentExpansion
|
||||||
|
}
|
||||||
|
|
||||||
|
// agents implements AgentInterface
|
||||||
|
type agents struct {
|
||||||
|
client rest.Interface
|
||||||
|
ns string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAgents returns a Agents
|
||||||
|
func newAgents(c *TowerV1alpha1Client, namespace string) *agents {
|
||||||
|
return &agents{
|
||||||
|
client: c.RESTClient(),
|
||||||
|
ns: namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get takes name of the agent, and returns the corresponding agent object, and an error if there is any.
|
||||||
|
func (c *agents) Get(name string, options v1.GetOptions) (result *v1alpha1.Agent, err error) {
|
||||||
|
result = &v1alpha1.Agent{}
|
||||||
|
err = c.client.Get().
|
||||||
|
Namespace(c.ns).
|
||||||
|
Resource("agents").
|
||||||
|
Name(name).
|
||||||
|
VersionedParams(&options, scheme.ParameterCodec).
|
||||||
|
Do().
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// List takes label and field selectors, and returns the list of Agents that match those selectors.
|
||||||
|
func (c *agents) List(opts v1.ListOptions) (result *v1alpha1.AgentList, err error) {
|
||||||
|
var timeout time.Duration
|
||||||
|
if opts.TimeoutSeconds != nil {
|
||||||
|
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||||
|
}
|
||||||
|
result = &v1alpha1.AgentList{}
|
||||||
|
err = c.client.Get().
|
||||||
|
Namespace(c.ns).
|
||||||
|
Resource("agents").
|
||||||
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
|
Timeout(timeout).
|
||||||
|
Do().
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch returns a watch.Interface that watches the requested agents.
|
||||||
|
func (c *agents) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||||
|
var timeout time.Duration
|
||||||
|
if opts.TimeoutSeconds != nil {
|
||||||
|
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||||
|
}
|
||||||
|
opts.Watch = true
|
||||||
|
return c.client.Get().
|
||||||
|
Namespace(c.ns).
|
||||||
|
Resource("agents").
|
||||||
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
|
Timeout(timeout).
|
||||||
|
Watch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes the representation of a agent and creates it. Returns the server's representation of the agent, and an error, if there is any.
|
||||||
|
func (c *agents) Create(agent *v1alpha1.Agent) (result *v1alpha1.Agent, err error) {
|
||||||
|
result = &v1alpha1.Agent{}
|
||||||
|
err = c.client.Post().
|
||||||
|
Namespace(c.ns).
|
||||||
|
Resource("agents").
|
||||||
|
Body(agent).
|
||||||
|
Do().
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update takes the representation of a agent and updates it. Returns the server's representation of the agent, and an error, if there is any.
|
||||||
|
func (c *agents) Update(agent *v1alpha1.Agent) (result *v1alpha1.Agent, err error) {
|
||||||
|
result = &v1alpha1.Agent{}
|
||||||
|
err = c.client.Put().
|
||||||
|
Namespace(c.ns).
|
||||||
|
Resource("agents").
|
||||||
|
Name(agent.Name).
|
||||||
|
Body(agent).
|
||||||
|
Do().
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatus was generated because the type contains a Status member.
|
||||||
|
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||||
|
|
||||||
|
func (c *agents) UpdateStatus(agent *v1alpha1.Agent) (result *v1alpha1.Agent, err error) {
|
||||||
|
result = &v1alpha1.Agent{}
|
||||||
|
err = c.client.Put().
|
||||||
|
Namespace(c.ns).
|
||||||
|
Resource("agents").
|
||||||
|
Name(agent.Name).
|
||||||
|
SubResource("status").
|
||||||
|
Body(agent).
|
||||||
|
Do().
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete takes name of the agent and deletes it. Returns an error if one occurs.
|
||||||
|
func (c *agents) Delete(name string, options *v1.DeleteOptions) error {
|
||||||
|
return c.client.Delete().
|
||||||
|
Namespace(c.ns).
|
||||||
|
Resource("agents").
|
||||||
|
Name(name).
|
||||||
|
Body(options).
|
||||||
|
Do().
|
||||||
|
Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCollection deletes a collection of objects.
|
||||||
|
func (c *agents) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||||
|
var timeout time.Duration
|
||||||
|
if listOptions.TimeoutSeconds != nil {
|
||||||
|
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
|
||||||
|
}
|
||||||
|
return c.client.Delete().
|
||||||
|
Namespace(c.ns).
|
||||||
|
Resource("agents").
|
||||||
|
VersionedParams(&listOptions, scheme.ParameterCodec).
|
||||||
|
Timeout(timeout).
|
||||||
|
Body(options).
|
||||||
|
Do().
|
||||||
|
Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch applies the patch and returns the patched agent.
|
||||||
|
func (c *agents) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Agent, err error) {
|
||||||
|
result = &v1alpha1.Agent{}
|
||||||
|
err = c.client.Patch(pt).
|
||||||
|
Namespace(c.ns).
|
||||||
|
Resource("agents").
|
||||||
|
SubResource(subresources...).
|
||||||
|
Name(name).
|
||||||
|
Body(data).
|
||||||
|
Do().
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
20
pkg/client/clientset/versioned/typed/tower/v1alpha1/doc.go
Normal file
20
pkg/client/clientset/versioned/typed/tower/v1alpha1/doc.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by client-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
// This package has the automatically generated typed clients.
|
||||||
|
package v1alpha1
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by client-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package fake has the automatically generated clients.
|
||||||
|
package fake
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by client-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package fake
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
labels "k8s.io/apimachinery/pkg/labels"
|
||||||
|
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
watch "k8s.io/apimachinery/pkg/watch"
|
||||||
|
testing "k8s.io/client-go/testing"
|
||||||
|
v1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakeAgents implements AgentInterface
|
||||||
|
type FakeAgents struct {
|
||||||
|
Fake *FakeTowerV1alpha1
|
||||||
|
ns string
|
||||||
|
}
|
||||||
|
|
||||||
|
var agentsResource = schema.GroupVersionResource{Group: "tower.kubesphere.io", Version: "v1alpha1", Resource: "agents"}
|
||||||
|
|
||||||
|
var agentsKind = schema.GroupVersionKind{Group: "tower.kubesphere.io", Version: "v1alpha1", Kind: "Agent"}
|
||||||
|
|
||||||
|
// Get takes name of the agent, and returns the corresponding agent object, and an error if there is any.
|
||||||
|
func (c *FakeAgents) Get(name string, options v1.GetOptions) (result *v1alpha1.Agent, err error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewGetAction(agentsResource, c.ns, name), &v1alpha1.Agent{})
|
||||||
|
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.Agent), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// List takes label and field selectors, and returns the list of Agents that match those selectors.
|
||||||
|
func (c *FakeAgents) List(opts v1.ListOptions) (result *v1alpha1.AgentList, err error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewListAction(agentsResource, agentsKind, c.ns, opts), &v1alpha1.AgentList{})
|
||||||
|
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||||
|
if label == nil {
|
||||||
|
label = labels.Everything()
|
||||||
|
}
|
||||||
|
list := &v1alpha1.AgentList{ListMeta: obj.(*v1alpha1.AgentList).ListMeta}
|
||||||
|
for _, item := range obj.(*v1alpha1.AgentList).Items {
|
||||||
|
if label.Matches(labels.Set(item.Labels)) {
|
||||||
|
list.Items = append(list.Items, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch returns a watch.Interface that watches the requested agents.
|
||||||
|
func (c *FakeAgents) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||||
|
return c.Fake.
|
||||||
|
InvokesWatch(testing.NewWatchAction(agentsResource, c.ns, opts))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes the representation of a agent and creates it. Returns the server's representation of the agent, and an error, if there is any.
|
||||||
|
func (c *FakeAgents) Create(agent *v1alpha1.Agent) (result *v1alpha1.Agent, err error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewCreateAction(agentsResource, c.ns, agent), &v1alpha1.Agent{})
|
||||||
|
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.Agent), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update takes the representation of a agent and updates it. Returns the server's representation of the agent, and an error, if there is any.
|
||||||
|
func (c *FakeAgents) Update(agent *v1alpha1.Agent) (result *v1alpha1.Agent, err error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewUpdateAction(agentsResource, c.ns, agent), &v1alpha1.Agent{})
|
||||||
|
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.Agent), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatus was generated because the type contains a Status member.
|
||||||
|
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||||
|
func (c *FakeAgents) UpdateStatus(agent *v1alpha1.Agent) (*v1alpha1.Agent, error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewUpdateSubresourceAction(agentsResource, "status", c.ns, agent), &v1alpha1.Agent{})
|
||||||
|
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.Agent), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete takes name of the agent and deletes it. Returns an error if one occurs.
|
||||||
|
func (c *FakeAgents) Delete(name string, options *v1.DeleteOptions) error {
|
||||||
|
_, err := c.Fake.
|
||||||
|
Invokes(testing.NewDeleteAction(agentsResource, c.ns, name), &v1alpha1.Agent{})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCollection deletes a collection of objects.
|
||||||
|
func (c *FakeAgents) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||||
|
action := testing.NewDeleteCollectionAction(agentsResource, c.ns, listOptions)
|
||||||
|
|
||||||
|
_, err := c.Fake.Invokes(action, &v1alpha1.AgentList{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch applies the patch and returns the patched agent.
|
||||||
|
func (c *FakeAgents) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Agent, err error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewPatchSubresourceAction(agentsResource, c.ns, name, pt, data, subresources...), &v1alpha1.Agent{})
|
||||||
|
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.Agent), err
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by client-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package fake
|
||||||
|
|
||||||
|
import (
|
||||||
|
rest "k8s.io/client-go/rest"
|
||||||
|
testing "k8s.io/client-go/testing"
|
||||||
|
v1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/tower/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeTowerV1alpha1 struct {
|
||||||
|
*testing.Fake
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeTowerV1alpha1) Agents(namespace string) v1alpha1.AgentInterface {
|
||||||
|
return &FakeAgents{c, namespace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RESTClient returns a RESTClient that is used to communicate
|
||||||
|
// with API server by this client implementation.
|
||||||
|
func (c *FakeTowerV1alpha1) RESTClient() rest.Interface {
|
||||||
|
var ret *rest.RESTClient
|
||||||
|
return ret
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by client-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
type AgentExpansion interface{}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by client-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
rest "k8s.io/client-go/rest"
|
||||||
|
v1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||||
|
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TowerV1alpha1Interface interface {
|
||||||
|
RESTClient() rest.Interface
|
||||||
|
AgentsGetter
|
||||||
|
}
|
||||||
|
|
||||||
|
// TowerV1alpha1Client is used to interact with features provided by the tower.kubesphere.io group.
|
||||||
|
type TowerV1alpha1Client struct {
|
||||||
|
restClient rest.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TowerV1alpha1Client) Agents(namespace string) AgentInterface {
|
||||||
|
return newAgents(c, namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewForConfig creates a new TowerV1alpha1Client for the given config.
|
||||||
|
func NewForConfig(c *rest.Config) (*TowerV1alpha1Client, error) {
|
||||||
|
config := *c
|
||||||
|
if err := setConfigDefaults(&config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client, err := rest.RESTClientFor(&config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &TowerV1alpha1Client{client}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewForConfigOrDie creates a new TowerV1alpha1Client for the given config and
|
||||||
|
// panics if there is an error in the config.
|
||||||
|
func NewForConfigOrDie(c *rest.Config) *TowerV1alpha1Client {
|
||||||
|
client, err := NewForConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new TowerV1alpha1Client for the given RESTClient.
|
||||||
|
func New(c rest.Interface) *TowerV1alpha1Client {
|
||||||
|
return &TowerV1alpha1Client{c}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setConfigDefaults(config *rest.Config) error {
|
||||||
|
gv := v1alpha1.SchemeGroupVersion
|
||||||
|
config.GroupVersion = &gv
|
||||||
|
config.APIPath = "/apis"
|
||||||
|
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
|
if config.UserAgent == "" {
|
||||||
|
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RESTClient returns a RESTClient that is used to communicate
|
||||||
|
// with API server by this client implementation.
|
||||||
|
func (c *TowerV1alpha1Client) RESTClient() rest.Interface {
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.restClient
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import (
|
|||||||
network "kubesphere.io/kubesphere/pkg/client/informers/externalversions/network"
|
network "kubesphere.io/kubesphere/pkg/client/informers/externalversions/network"
|
||||||
servicemesh "kubesphere.io/kubesphere/pkg/client/informers/externalversions/servicemesh"
|
servicemesh "kubesphere.io/kubesphere/pkg/client/informers/externalversions/servicemesh"
|
||||||
tenant "kubesphere.io/kubesphere/pkg/client/informers/externalversions/tenant"
|
tenant "kubesphere.io/kubesphere/pkg/client/informers/externalversions/tenant"
|
||||||
|
tower "kubesphere.io/kubesphere/pkg/client/informers/externalversions/tower"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SharedInformerOption defines the functional option type for SharedInformerFactory.
|
// SharedInformerOption defines the functional option type for SharedInformerFactory.
|
||||||
@@ -179,6 +180,7 @@ type SharedInformerFactory interface {
|
|||||||
Network() network.Interface
|
Network() network.Interface
|
||||||
Servicemesh() servicemesh.Interface
|
Servicemesh() servicemesh.Interface
|
||||||
Tenant() tenant.Interface
|
Tenant() tenant.Interface
|
||||||
|
Tower() tower.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *sharedInformerFactory) Devops() devops.Interface {
|
func (f *sharedInformerFactory) Devops() devops.Interface {
|
||||||
@@ -196,3 +198,7 @@ func (f *sharedInformerFactory) Servicemesh() servicemesh.Interface {
|
|||||||
func (f *sharedInformerFactory) Tenant() tenant.Interface {
|
func (f *sharedInformerFactory) Tenant() tenant.Interface {
|
||||||
return tenant.New(f, f.namespace, f.tweakListOptions)
|
return tenant.New(f, f.namespace, f.tweakListOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *sharedInformerFactory) Tower() tower.Interface {
|
||||||
|
return tower.New(f, f.namespace, f.tweakListOptions)
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1"
|
networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1"
|
||||||
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2"
|
v1alpha2 "kubesphere.io/kubesphere/pkg/apis/servicemesh/v1alpha2"
|
||||||
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
||||||
|
towerv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenericInformer is type of SharedIndexInformer which will locate and delegate to other
|
// GenericInformer is type of SharedIndexInformer which will locate and delegate to other
|
||||||
@@ -88,6 +89,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
|
|||||||
case tenantv1alpha1.SchemeGroupVersion.WithResource("workspaces"):
|
case tenantv1alpha1.SchemeGroupVersion.WithResource("workspaces"):
|
||||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Tenant().V1alpha1().Workspaces().Informer()}, nil
|
return &genericInformer{resource: resource.GroupResource(), informer: f.Tenant().V1alpha1().Workspaces().Informer()}, nil
|
||||||
|
|
||||||
|
// Group=tower.kubesphere.io, Version=v1alpha1
|
||||||
|
case towerv1alpha1.SchemeGroupVersion.WithResource("agents"):
|
||||||
|
return &genericInformer{resource: resource.GroupResource(), informer: f.Tower().V1alpha1().Agents().Informer()}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("no informer found for %v", resource)
|
return nil, fmt.Errorf("no informer found for %v", resource)
|
||||||
|
|||||||
46
pkg/client/informers/externalversions/tower/interface.go
Normal file
46
pkg/client/informers/externalversions/tower/interface.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by informer-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package tower
|
||||||
|
|
||||||
|
import (
|
||||||
|
internalinterfaces "kubesphere.io/kubesphere/pkg/client/informers/externalversions/internalinterfaces"
|
||||||
|
v1alpha1 "kubesphere.io/kubesphere/pkg/client/informers/externalversions/tower/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interface provides access to each of this group's versions.
|
||||||
|
type Interface interface {
|
||||||
|
// V1alpha1 provides access to shared informers for resources in V1alpha1.
|
||||||
|
V1alpha1() v1alpha1.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
type group struct {
|
||||||
|
factory internalinterfaces.SharedInformerFactory
|
||||||
|
namespace string
|
||||||
|
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Interface.
|
||||||
|
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
|
||||||
|
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||||
|
}
|
||||||
|
|
||||||
|
// V1alpha1 returns a new v1alpha1.Interface.
|
||||||
|
func (g *group) V1alpha1() v1alpha1.Interface {
|
||||||
|
return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by informer-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
time "time"
|
||||||
|
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
watch "k8s.io/apimachinery/pkg/watch"
|
||||||
|
cache "k8s.io/client-go/tools/cache"
|
||||||
|
towerv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||||
|
versioned "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||||
|
internalinterfaces "kubesphere.io/kubesphere/pkg/client/informers/externalversions/internalinterfaces"
|
||||||
|
v1alpha1 "kubesphere.io/kubesphere/pkg/client/listers/tower/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AgentInformer provides access to a shared informer and lister for
|
||||||
|
// Agents.
|
||||||
|
type AgentInformer interface {
|
||||||
|
Informer() cache.SharedIndexInformer
|
||||||
|
Lister() v1alpha1.AgentLister
|
||||||
|
}
|
||||||
|
|
||||||
|
type agentInformer struct {
|
||||||
|
factory internalinterfaces.SharedInformerFactory
|
||||||
|
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||||
|
namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAgentInformer constructs a new informer for Agent type.
|
||||||
|
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||||
|
// one. This reduces memory footprint and number of connections to the server.
|
||||||
|
func NewAgentInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||||
|
return NewFilteredAgentInformer(client, namespace, resyncPeriod, indexers, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFilteredAgentInformer constructs a new informer for Agent type.
|
||||||
|
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||||
|
// one. This reduces memory footprint and number of connections to the server.
|
||||||
|
func NewFilteredAgentInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||||
|
return cache.NewSharedIndexInformer(
|
||||||
|
&cache.ListWatch{
|
||||||
|
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||||
|
if tweakListOptions != nil {
|
||||||
|
tweakListOptions(&options)
|
||||||
|
}
|
||||||
|
return client.TowerV1alpha1().Agents(namespace).List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||||
|
if tweakListOptions != nil {
|
||||||
|
tweakListOptions(&options)
|
||||||
|
}
|
||||||
|
return client.TowerV1alpha1().Agents(namespace).Watch(options)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&towerv1alpha1.Agent{},
|
||||||
|
resyncPeriod,
|
||||||
|
indexers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *agentInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||||
|
return NewFilteredAgentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *agentInformer) Informer() cache.SharedIndexInformer {
|
||||||
|
return f.factory.InformerFor(&towerv1alpha1.Agent{}, f.defaultInformer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *agentInformer) Lister() v1alpha1.AgentLister {
|
||||||
|
return v1alpha1.NewAgentLister(f.Informer().GetIndexer())
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by informer-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
internalinterfaces "kubesphere.io/kubesphere/pkg/client/informers/externalversions/internalinterfaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interface provides access to all the informers in this group version.
|
||||||
|
type Interface interface {
|
||||||
|
// Agents returns a AgentInformer.
|
||||||
|
Agents() AgentInformer
|
||||||
|
}
|
||||||
|
|
||||||
|
type version struct {
|
||||||
|
factory internalinterfaces.SharedInformerFactory
|
||||||
|
namespace string
|
||||||
|
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Interface.
|
||||||
|
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
|
||||||
|
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agents returns a AgentInformer.
|
||||||
|
func (v *version) Agents() AgentInformer {
|
||||||
|
return &agentInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||||
|
}
|
||||||
94
pkg/client/listers/tower/v1alpha1/agent.go
Normal file
94
pkg/client/listers/tower/v1alpha1/agent.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by lister-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
v1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AgentLister helps list Agents.
|
||||||
|
type AgentLister interface {
|
||||||
|
// List lists all Agents in the indexer.
|
||||||
|
List(selector labels.Selector) (ret []*v1alpha1.Agent, err error)
|
||||||
|
// Agents returns an object that can list and get Agents.
|
||||||
|
Agents(namespace string) AgentNamespaceLister
|
||||||
|
AgentListerExpansion
|
||||||
|
}
|
||||||
|
|
||||||
|
// agentLister implements the AgentLister interface.
|
||||||
|
type agentLister struct {
|
||||||
|
indexer cache.Indexer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAgentLister returns a new AgentLister.
|
||||||
|
func NewAgentLister(indexer cache.Indexer) AgentLister {
|
||||||
|
return &agentLister{indexer: indexer}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists all Agents in the indexer.
|
||||||
|
func (s *agentLister) List(selector labels.Selector) (ret []*v1alpha1.Agent, err error) {
|
||||||
|
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
|
||||||
|
ret = append(ret, m.(*v1alpha1.Agent))
|
||||||
|
})
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agents returns an object that can list and get Agents.
|
||||||
|
func (s *agentLister) Agents(namespace string) AgentNamespaceLister {
|
||||||
|
return agentNamespaceLister{indexer: s.indexer, namespace: namespace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentNamespaceLister helps list and get Agents.
|
||||||
|
type AgentNamespaceLister interface {
|
||||||
|
// List lists all Agents in the indexer for a given namespace.
|
||||||
|
List(selector labels.Selector) (ret []*v1alpha1.Agent, err error)
|
||||||
|
// Get retrieves the Agent from the indexer for a given namespace and name.
|
||||||
|
Get(name string) (*v1alpha1.Agent, error)
|
||||||
|
AgentNamespaceListerExpansion
|
||||||
|
}
|
||||||
|
|
||||||
|
// agentNamespaceLister implements the AgentNamespaceLister
|
||||||
|
// interface.
|
||||||
|
type agentNamespaceLister struct {
|
||||||
|
indexer cache.Indexer
|
||||||
|
namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists all Agents in the indexer for a given namespace.
|
||||||
|
func (s agentNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Agent, err error) {
|
||||||
|
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
|
||||||
|
ret = append(ret, m.(*v1alpha1.Agent))
|
||||||
|
})
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves the Agent from the indexer for a given namespace and name.
|
||||||
|
func (s agentNamespaceLister) Get(name string) (*v1alpha1.Agent, error) {
|
||||||
|
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.NewNotFound(v1alpha1.Resource("agent"), name)
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.Agent), nil
|
||||||
|
}
|
||||||
27
pkg/client/listers/tower/v1alpha1/expansion_generated.go
Normal file
27
pkg/client/listers/tower/v1alpha1/expansion_generated.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The KubeSphere authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by lister-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
// AgentListerExpansion allows custom methods to be added to
|
||||||
|
// AgentLister.
|
||||||
|
type AgentListerExpansion interface{}
|
||||||
|
|
||||||
|
// AgentNamespaceListerExpansion allows custom methods to be added to
|
||||||
|
// AgentNamespaceLister.
|
||||||
|
type AgentNamespaceListerExpansion interface{}
|
||||||
@@ -32,11 +32,9 @@ import (
|
|||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
||||||
"kubesphere.io/kubesphere/pkg/constants"
|
"kubesphere.io/kubesphere/pkg/constants"
|
||||||
"kubesphere.io/kubesphere/pkg/models/iam"
|
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
|
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
|
||||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
||||||
"openpitrix.io/openpitrix/pkg/pb"
|
"openpitrix.io/openpitrix/pkg/pb"
|
||||||
"reflect"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||||
@@ -183,14 +181,6 @@ func (r *ReconcileNamespace) Reconcile(request reconcile.Request) (reconcile.Res
|
|||||||
return reconcile.Result{}, err
|
return reconcile.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = r.checkAndCreateRoles(instance); err != nil {
|
|
||||||
return reconcile.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = r.checkAndCreateRoleBindings(instance); err != nil {
|
|
||||||
return reconcile.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.checkAndCreateRuntime(instance); err != nil {
|
if err := r.checkAndCreateRuntime(instance); err != nil {
|
||||||
return reconcile.Result{}, err
|
return reconcile.Result{}, err
|
||||||
}
|
}
|
||||||
@@ -210,152 +200,6 @@ func (r *ReconcileNamespace) isControlledByWorkspace(namespace *corev1.Namespace
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create default roles
|
|
||||||
func (r *ReconcileNamespace) checkAndCreateRoles(namespace *corev1.Namespace) error {
|
|
||||||
for _, role := range defaultRoles {
|
|
||||||
found := &rbac.Role{}
|
|
||||||
err := r.Get(context.TODO(), types.NamespacedName{Namespace: namespace.Name, Name: role.Name}, found)
|
|
||||||
if err != nil {
|
|
||||||
if errors.IsNotFound(err) {
|
|
||||||
role := role.DeepCopy()
|
|
||||||
role.Namespace = namespace.Name
|
|
||||||
err = r.Create(context.TODO(), role)
|
|
||||||
if err != nil {
|
|
||||||
klog.Error(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
klog.Error(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(found.Rules, role.Rules) {
|
|
||||||
found.Rules = role.Rules
|
|
||||||
if err := r.Update(context.TODO(), found); err != nil {
|
|
||||||
klog.Error(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ReconcileNamespace) checkAndCreateRoleBindings(namespace *corev1.Namespace) error {
|
|
||||||
|
|
||||||
workspaceName := namespace.Labels[constants.WorkspaceLabelKey]
|
|
||||||
creatorName := namespace.Annotations[constants.CreatorAnnotationKey]
|
|
||||||
|
|
||||||
creator := rbac.Subject{APIGroup: "rbac.authorization.k8s.io", Kind: "User", Name: creatorName}
|
|
||||||
|
|
||||||
workspaceAdminBinding := &rbac.ClusterRoleBinding{}
|
|
||||||
|
|
||||||
err := r.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf("workspace:%s:admin", workspaceName)}, workspaceAdminBinding)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
adminBinding := &rbac.RoleBinding{}
|
|
||||||
adminBinding.Name = admin.Name
|
|
||||||
adminBinding.Namespace = namespace.Name
|
|
||||||
adminBinding.RoleRef = rbac.RoleRef{Name: admin.Name, APIGroup: "rbac.authorization.k8s.io", Kind: "Role"}
|
|
||||||
adminBinding.Subjects = workspaceAdminBinding.Subjects
|
|
||||||
|
|
||||||
if creator.Name != "" {
|
|
||||||
if adminBinding.Subjects == nil {
|
|
||||||
adminBinding.Subjects = make([]rbac.Subject, 0)
|
|
||||||
}
|
|
||||||
if !iam.ContainsUser(adminBinding.Subjects, creatorName) {
|
|
||||||
adminBinding.Subjects = append(adminBinding.Subjects, creator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
found := &rbac.RoleBinding{}
|
|
||||||
|
|
||||||
err = r.Get(context.TODO(), types.NamespacedName{Namespace: namespace.Name, Name: adminBinding.Name}, found)
|
|
||||||
|
|
||||||
if errors.IsNotFound(err) {
|
|
||||||
err = r.Create(context.TODO(), adminBinding)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("creating role binding namespace: %s,role binding: %s, error: %s", namespace.Name, adminBinding.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
found = adminBinding
|
|
||||||
} else if err != nil {
|
|
||||||
klog.Errorf("get role binding namespace: %s,role binding: %s, error: %s", namespace.Name, adminBinding.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(found.RoleRef, adminBinding.RoleRef) {
|
|
||||||
err = r.Delete(context.TODO(), found)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("deleting role binding namespace: %s, role binding: %s, error: %s", namespace.Name, adminBinding.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = fmt.Errorf("conflict role binding %s.%s, waiting for recreate", namespace.Name, adminBinding.Name)
|
|
||||||
klog.Errorf("conflict role binding namespace: %s, role binding: %s, error: %s", namespace.Name, adminBinding.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(found.Subjects, adminBinding.Subjects) {
|
|
||||||
found.Subjects = adminBinding.Subjects
|
|
||||||
err = r.Update(context.TODO(), found)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("updating role binding namespace: %s, role binding: %s, error: %s", namespace.Name, adminBinding.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
workspaceViewerBinding := &rbac.ClusterRoleBinding{}
|
|
||||||
|
|
||||||
err = r.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf("workspace:%s:viewer", workspaceName)}, workspaceViewerBinding)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
viewerBinding := &rbac.RoleBinding{}
|
|
||||||
viewerBinding.Name = viewer.Name
|
|
||||||
viewerBinding.Namespace = namespace.Name
|
|
||||||
viewerBinding.RoleRef = rbac.RoleRef{Name: viewer.Name, APIGroup: "rbac.authorization.k8s.io", Kind: "Role"}
|
|
||||||
viewerBinding.Subjects = workspaceViewerBinding.Subjects
|
|
||||||
|
|
||||||
err = r.Get(context.TODO(), types.NamespacedName{Namespace: namespace.Name, Name: viewerBinding.Name}, found)
|
|
||||||
|
|
||||||
if errors.IsNotFound(err) {
|
|
||||||
err = r.Create(context.TODO(), viewerBinding)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("creating role binding namespace: %s, role binding: %s, error: %s", namespace.Name, viewerBinding.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
found = viewerBinding
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(found.RoleRef, viewerBinding.RoleRef) {
|
|
||||||
err = r.Delete(context.TODO(), found)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("deleting conflict role binding namespace: %s, role binding: %s, %s", namespace.Name, viewerBinding.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = fmt.Errorf("conflict role binding %s.%s, waiting for recreate", namespace.Name, viewerBinding.Name)
|
|
||||||
klog.Errorf("conflict role binding namespace: %s, role binding: %s, error: %s", namespace.Name, viewerBinding.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(found.Subjects, viewerBinding.Subjects) {
|
|
||||||
found.Subjects = viewerBinding.Subjects
|
|
||||||
err = r.Update(context.TODO(), found)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("updating role binding namespace: %s, role binding: %s, error: %s", namespace.Name, viewerBinding.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create openpitrix runtime
|
// Create openpitrix runtime
|
||||||
func (r *ReconcileNamespace) checkAndCreateRuntime(namespace *corev1.Namespace) error {
|
func (r *ReconcileNamespace) checkAndCreateRuntime(namespace *corev1.Namespace) error {
|
||||||
|
|
||||||
|
|||||||
@@ -1,483 +1,79 @@
|
|||||||
package v1alpha2
|
package v1alpha2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
"github.com/go-ldap/ldap"
|
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
"k8s.io/klog"
|
|
||||||
"kubesphere.io/kubesphere/pkg/api"
|
|
||||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/api/iam/v1alpha2"
|
|
||||||
"kubesphere.io/kubesphere/pkg/constants"
|
|
||||||
"kubesphere.io/kubesphere/pkg/informers"
|
"kubesphere.io/kubesphere/pkg/informers"
|
||||||
"kubesphere.io/kubesphere/pkg/models/iam"
|
"kubesphere.io/kubesphere/pkg/models/iam/am"
|
||||||
"kubesphere.io/kubesphere/pkg/models/iam/policy"
|
"kubesphere.io/kubesphere/pkg/models/iam/im"
|
||||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
|
|
||||||
apierr "kubesphere.io/kubesphere/pkg/server/errors"
|
|
||||||
"kubesphere.io/kubesphere/pkg/server/params"
|
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
||||||
ldappool "kubesphere.io/kubesphere/pkg/simple/client/ldap"
|
ldappool "kubesphere.io/kubesphere/pkg/simple/client/ldap"
|
||||||
"kubesphere.io/kubesphere/pkg/utils/iputil"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
iamapi "kubesphere.io/kubesphere/pkg/api/iam"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
kindTokenReview = "TokenReview"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type iamHandler struct {
|
type iamHandler struct {
|
||||||
amOperator iam.AccessManagementInterface
|
amOperator am.AccessManagementInterface
|
||||||
imOperator iam.IdentityManagementInterface
|
imOperator im.IdentityManagementInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *iamapi.AuthenticationOptions) *iamHandler {
|
func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) *iamHandler {
|
||||||
return &iamHandler{
|
return &iamHandler{
|
||||||
amOperator: iam.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
|
amOperator: am.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
|
||||||
imOperator: iam.NewIMOperator(ldapClient, cacheClient, options),
|
imOperator: im.NewLDAPOperator(ldapClient),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement webhook authentication interface
|
|
||||||
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
|
|
||||||
func (h *iamHandler) TokenReviewHandler(req *restful.Request, resp *restful.Response) {
|
|
||||||
var tokenReview iamv1alpha2.TokenReview
|
|
||||||
|
|
||||||
err := req.ReadEntity(&tokenReview)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Error(err)
|
|
||||||
api.HandleBadRequest(resp, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tokenReview.Validate(); err != nil {
|
|
||||||
klog.Error(err)
|
|
||||||
api.HandleBadRequest(resp, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := h.imOperator.VerifyToken(tokenReview.Spec.Token)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
success := iamv1alpha2.TokenReview{APIVersion: tokenReview.APIVersion,
|
|
||||||
Kind: kindTokenReview,
|
|
||||||
Status: &iamv1alpha2.Status{
|
|
||||||
Authenticated: true,
|
|
||||||
User: map[string]interface{}{"username": user.Username, "uid": user.Username, "groups": user.Groups},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(success)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *iamHandler) Login(req *restful.Request, resp *restful.Response) {
|
|
||||||
var loginRequest iamv1alpha2.LoginRequest
|
|
||||||
|
|
||||||
err := req.ReadEntity(&loginRequest)
|
|
||||||
|
|
||||||
if err != nil || loginRequest.Username == "" || loginRequest.Password == "" {
|
|
||||||
err = errors.New("incorrect username or password")
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
resp.WriteHeaderAndEntity(http.StatusUnauthorized, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := iputil.RemoteIp(req.Request)
|
|
||||||
|
|
||||||
token, err := h.imOperator.Login(loginRequest.Username, loginRequest.Password, ip)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err == iam.AuthRateLimitExceeded {
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
resp.WriteHeaderAndEntity(http.StatusTooManyRequests, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
resp.WriteHeaderAndEntity(http.StatusUnauthorized, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(token)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *iamHandler) CreateUser(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) CreateUser(req *restful.Request, resp *restful.Response) {
|
||||||
var createRequest iamv1alpha2.CreateUserRequest
|
panic("implement me")
|
||||||
err := req.ReadEntity(&createRequest)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
api.HandleBadRequest(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := createRequest.Validate(); err != nil {
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
api.HandleBadRequest(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
created, err := h.imOperator.CreateUser(createRequest.User)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err == iam.UserAlreadyExists {
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
resp.WriteHeaderAndEntity(http.StatusConflict, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.amOperator.CreateClusterRoleBinding(created.Username, createRequest.ClusterRole)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(created)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) DeleteUser(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) DeleteUser(req *restful.Request, resp *restful.Response) {
|
||||||
username := req.PathParameter("user")
|
panic("implement me")
|
||||||
operator := req.HeaderParameter(constants.UserNameHeader)
|
|
||||||
|
|
||||||
if operator == username {
|
|
||||||
err := errors.New("cannot delete yourself")
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
api.HandleForbidden(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := h.amOperator.UnBindAllRoles(username)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.imOperator.DeleteUser(username)
|
|
||||||
|
|
||||||
// TODO release user resources
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(apierr.None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) ModifyUser(request *restful.Request, response *restful.Response) {
|
func (h *iamHandler) ModifyUser(request *restful.Request, response *restful.Response) {
|
||||||
|
panic("implement me")
|
||||||
username := request.PathParameter("user")
|
|
||||||
operator := request.HeaderParameter(constants.UserNameHeader)
|
|
||||||
var modifyUserRequest iamv1alpha2.ModifyUserRequest
|
|
||||||
|
|
||||||
err := request.ReadEntity(&modifyUserRequest)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
api.HandleBadRequest(response, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if username != modifyUserRequest.Username {
|
|
||||||
err = fmt.Errorf("the name of user (%s) does not match the name on the URL (%s)", modifyUserRequest.Username, username)
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
api.HandleBadRequest(response, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = modifyUserRequest.Validate(); err != nil {
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
api.HandleBadRequest(response, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// change password by self
|
|
||||||
if operator == modifyUserRequest.Username && modifyUserRequest.Password != "" {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := h.imOperator.ModifyUser(modifyUserRequest.User)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(response, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO modify cluster role
|
|
||||||
|
|
||||||
response.WriteEntity(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) DescribeUser(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) DescribeUser(req *restful.Request, resp *restful.Response) {
|
||||||
username := req.PathParameter("user")
|
panic("implement me")
|
||||||
|
|
||||||
user, err := h.imOperator.DescribeUser(username)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err == iam.UserNotExists {
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
api.HandleNotFound(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO append more user info
|
|
||||||
clusterRole, err := h.amOperator.GetClusterRole(username)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result := iamv1alpha2.UserDetail{
|
|
||||||
User: user,
|
|
||||||
ClusterRole: clusterRole.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) ListUsers(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) ListUsers(req *restful.Request, resp *restful.Response) {
|
||||||
|
panic("implement me")
|
||||||
limit, offset := params.ParsePaging(req)
|
|
||||||
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
|
|
||||||
reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true)
|
|
||||||
conditions, err := params.ParseConditions(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
api.HandleBadRequest(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := h.imOperator.ListUsers(conditions, orderBy, reverse, limit, offset)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) ListUserRoles(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) ListUserRoles(req *restful.Request, resp *restful.Response) {
|
||||||
|
panic("implement me")
|
||||||
username := req.PathParameter("user")
|
|
||||||
|
|
||||||
roles, err := h.imOperator.GetUserRoles(username)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(roles)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) ListRoles(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) ListRoles(req *restful.Request, resp *restful.Response) {
|
||||||
namespace := req.PathParameter("namespace")
|
|
||||||
limit, offset := params.ParsePaging(req)
|
|
||||||
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
|
|
||||||
reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true)
|
|
||||||
conditions, err := params.ParseConditions(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
api.HandleBadRequest(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := h.amOperator.ListRoles(namespace, conditions, orderBy, reverse, limit, offset)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteAsJson(result)
|
|
||||||
|
|
||||||
|
panic("implement me")
|
||||||
}
|
}
|
||||||
func (h *iamHandler) ListClusterRoles(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) ListClusterRoles(req *restful.Request, resp *restful.Response) {
|
||||||
limit, offset := params.ParsePaging(req)
|
panic("implement me")
|
||||||
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
|
|
||||||
reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true)
|
|
||||||
conditions, err := params.ParseConditions(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infoln(err)
|
|
||||||
api.HandleBadRequest(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := h.amOperator.ListClusterRoles(conditions, orderBy, reverse, limit, offset)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(result)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) ListRoleUsers(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) ListRoleUsers(req *restful.Request, resp *restful.Response) {
|
||||||
role := req.PathParameter("role")
|
panic("implement me")
|
||||||
namespace := req.PathParameter("namespace")
|
|
||||||
|
|
||||||
roleBindings, err := h.amOperator.ListRoleBindings(namespace, role)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result := make([]*iamapi.User, 0)
|
|
||||||
for _, roleBinding := range roleBindings {
|
|
||||||
for _, subject := range roleBinding.Subjects {
|
|
||||||
if subject.Kind == rbacv1.UserKind {
|
|
||||||
user, err := h.imOperator.DescribeUser(subject.Name)
|
|
||||||
// skip if user not exist
|
|
||||||
if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result = append(result, user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List users by namespace
|
// List users by namespace
|
||||||
func (h *iamHandler) ListNamespaceUsers(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) ListNamespaceUsers(req *restful.Request, resp *restful.Response) {
|
||||||
|
panic("implement me")
|
||||||
namespace := req.PathParameter("namespace")
|
|
||||||
|
|
||||||
roleBindings, err := h.amOperator.ListRoleBindings(namespace, "")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]*iamapi.User, 0)
|
|
||||||
for _, roleBinding := range roleBindings {
|
|
||||||
for _, subject := range roleBinding.Subjects {
|
|
||||||
if subject.Kind == rbacv1.UserKind {
|
|
||||||
user, err := h.imOperator.DescribeUser(subject.Name)
|
|
||||||
// skip if user not exist
|
|
||||||
if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result = append(result, user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) ListClusterRoleUsers(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) ListClusterRoleUsers(req *restful.Request, resp *restful.Response) {
|
||||||
clusterRole := req.PathParameter("clusterrole")
|
panic("implement me")
|
||||||
clusterRoleBindings, err := h.amOperator.ListClusterRoleBindings(clusterRole)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]*iamapi.User, 0)
|
|
||||||
for _, roleBinding := range clusterRoleBindings {
|
|
||||||
for _, subject := range roleBinding.Subjects {
|
|
||||||
if subject.Kind == rbacv1.UserKind {
|
|
||||||
user, err := h.imOperator.DescribeUser(subject.Name)
|
|
||||||
// skip if user not exist
|
|
||||||
if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result = append(result, user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *iamHandler) RulesMapping(req *restful.Request, resp *restful.Response) {
|
|
||||||
rules := policy.RoleRuleMapping
|
|
||||||
resp.WriteEntity(rules)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *iamHandler) ClusterRulesMapping(req *restful.Request, resp *restful.Response) {
|
|
||||||
rules := policy.ClusterRoleRuleMapping
|
|
||||||
resp.WriteEntity(rules)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) ListClusterRoleRules(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) ListClusterRoleRules(req *restful.Request, resp *restful.Response) {
|
||||||
clusterRole := req.PathParameter("clusterrole")
|
panic("implement me")
|
||||||
rules, err := h.amOperator.GetClusterRoleSimpleRules(clusterRole)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.WriteEntity(rules)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) ListRoleRules(req *restful.Request, resp *restful.Response) {
|
func (h *iamHandler) ListRoleRules(req *restful.Request, resp *restful.Response) {
|
||||||
namespace := req.PathParameter("namespace")
|
panic("implement me")
|
||||||
role := req.PathParameter("role")
|
|
||||||
|
|
||||||
rules, err := h.amOperator.GetRoleSimpleRules(namespace, role)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(rules)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *iamHandler) ListWorkspaceRoles(request *restful.Request, response *restful.Response) {
|
func (h *iamHandler) ListWorkspaceRoles(request *restful.Request, response *restful.Response) {
|
||||||
|
|||||||
@@ -20,16 +20,13 @@ package v1alpha2
|
|||||||
import (
|
import (
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
"github.com/emicklei/go-restful-openapi"
|
"github.com/emicklei/go-restful-openapi"
|
||||||
rbacv1 "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"
|
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/api/iam/v1alpha2"
|
|
||||||
"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"
|
||||||
"kubesphere.io/kubesphere/pkg/models"
|
"kubesphere.io/kubesphere/pkg/models"
|
||||||
"kubesphere.io/kubesphere/pkg/models/iam/policy"
|
|
||||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
||||||
@@ -41,111 +38,44 @@ const groupName = "iam.kubesphere.io"
|
|||||||
|
|
||||||
var GroupVersion = schema.GroupVersion{Group: groupName, Version: "v1alpha2"}
|
var GroupVersion = schema.GroupVersion{Group: groupName, Version: "v1alpha2"}
|
||||||
|
|
||||||
func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *iam.AuthenticationOptions) error {
|
func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) error {
|
||||||
ws := runtime.NewWebService(GroupVersion)
|
ws := runtime.NewWebService(GroupVersion)
|
||||||
|
|
||||||
handler := newIAMHandler(k8sClient, factory, ldapClient, cacheClient, options)
|
handler := newIAMHandler(k8sClient, factory, ldapClient, cacheClient, options)
|
||||||
|
|
||||||
ws.Route(ws.POST("/authenticate").
|
// implemented by create CRD object.
|
||||||
To(handler.TokenReviewHandler).
|
//ws.Route(ws.POST("/users"))
|
||||||
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.").
|
//ws.Route(ws.DELETE("/users/{user}"))
|
||||||
Reads(iamv1alpha2.TokenReview{}).
|
//ws.Route(ws.PUT("/users/{user}"))
|
||||||
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.TokenReview{}).
|
//ws.Route(ws.GET("/users/{user}"))
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
|
||||||
ws.Route(ws.POST("/login").
|
// TODO move to resources api
|
||||||
To(handler.Login).
|
//ws.Route(ws.GET("/users"))
|
||||||
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(iamv1alpha2.LoginRequest{}).
|
|
||||||
Returns(http.StatusOK, api.StatusOK, models.AuthGrantResponse{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
|
||||||
ws.Route(ws.POST("/users").
|
|
||||||
To(handler.CreateUser).
|
|
||||||
Doc("Create a user account.").
|
|
||||||
Reads(iamv1alpha2.CreateUserRequest{}).
|
|
||||||
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.UserDetail{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
|
||||||
ws.Route(ws.DELETE("/users/{user}").
|
|
||||||
To(handler.DeleteUser).
|
|
||||||
Doc("Delete the specified user.").
|
|
||||||
Param(ws.PathParameter("user", "username")).
|
|
||||||
Returns(http.StatusOK, api.StatusOK, errors.Error{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
|
||||||
ws.Route(ws.PUT("/users/{user}").
|
|
||||||
To(handler.ModifyUser).
|
|
||||||
Doc("Update information about the specified user.").
|
|
||||||
Param(ws.PathParameter("user", "username")).
|
|
||||||
Reads(iamv1alpha2.ModifyUserRequest{}).
|
|
||||||
Returns(http.StatusOK, api.StatusOK, errors.Error{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
|
||||||
ws.Route(ws.GET("/users/{user}").
|
|
||||||
To(handler.DescribeUser).
|
|
||||||
Doc("Describe the specified user.").
|
|
||||||
Param(ws.PathParameter("user", "username")).
|
|
||||||
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.UserDetail{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
|
||||||
ws.Route(ws.GET("/users").
|
|
||||||
To(handler.ListUsers).
|
|
||||||
Doc("List all users.").
|
|
||||||
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.ListUserResponse{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
|
||||||
ws.Route(ws.GET("/users/{user}/roles").
|
|
||||||
To(handler.ListUserRoles).
|
|
||||||
Doc("Retrieve all the roles that are assigned to the specified user.").
|
|
||||||
Param(ws.PathParameter("user", "username")).
|
|
||||||
Returns(http.StatusOK, api.StatusOK, []*rbacv1.Role{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
|
||||||
ws.Route(ws.GET("/namespaces/{namespace}/roles").
|
ws.Route(ws.GET("/namespaces/{namespace}/roles").
|
||||||
To(handler.ListRoles).
|
To(handler.ListRoles).
|
||||||
Doc("Retrieve the roles that are assigned to the user in the specified namespace.").
|
Doc("Retrieve the roles that are assigned to the user in the specified namespace.").
|
||||||
Param(ws.PathParameter("namespace", "kubernetes namespace")).
|
Param(ws.PathParameter("namespace", "kubernetes namespace")).
|
||||||
Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}).
|
Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}).
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||||
|
|
||||||
ws.Route(ws.GET("/clusterroles").
|
ws.Route(ws.GET("/clusterroles").
|
||||||
To(handler.ListClusterRoles).
|
To(handler.ListClusterRoles).
|
||||||
Doc("List all cluster roles.").
|
Doc("List all cluster roles.").
|
||||||
Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}).
|
Returns(http.StatusOK, api.StatusOK, models.PageableResponse{}).
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||||
ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/users").
|
|
||||||
To(handler.ListRoleUsers).
|
// TODO merge
|
||||||
Doc("Retrieve the users that are bound to the role in the specified namespace.").
|
//ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/users"))
|
||||||
Param(ws.PathParameter("namespace", "kubernetes namespace")).
|
|
||||||
Param(ws.PathParameter("role", "role name")).
|
|
||||||
Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.ListUserResponse{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
|
||||||
ws.Route(ws.GET("/namespaces/{namespace}/users").
|
ws.Route(ws.GET("/namespaces/{namespace}/users").
|
||||||
To(handler.ListNamespaceUsers).
|
To(handler.ListNamespaceUsers).
|
||||||
Doc("List all users in the specified namespace.").
|
Doc("List all users in the specified namespace.").
|
||||||
Param(ws.PathParameter("namespace", "kubernetes namespace")).
|
Param(ws.PathParameter("namespace", "kubernetes namespace")).
|
||||||
Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.ListUserResponse{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||||
ws.Route(ws.GET("/clusterroles/{clusterrole}/users").
|
ws.Route(ws.GET("/clusterroles/{clusterrole}/users").
|
||||||
To(handler.ListClusterRoleUsers).
|
To(handler.ListClusterRoleUsers).
|
||||||
Doc("List all users that are bound to the specified cluster role.").
|
Doc("List all users that are bound to the specified cluster role.").
|
||||||
Param(ws.PathParameter("clusterrole", "cluster role name")).
|
Param(ws.PathParameter("clusterrole", "cluster role name")).
|
||||||
Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.ListUserResponse{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
|
||||||
ws.Route(ws.GET("/clusterroles/{clusterrole}/rules").
|
|
||||||
To(handler.ListClusterRoleRules).
|
|
||||||
Doc("List all policy rules of the specified cluster role.").
|
|
||||||
Param(ws.PathParameter("clusterrole", "cluster role name")).
|
|
||||||
Returns(http.StatusOK, api.StatusOK, []policy.SimpleRule{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
|
||||||
ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/rules").
|
|
||||||
To(handler.ListRoleRules).
|
|
||||||
Doc("List all policy rules of the specified role in the given namespace.").
|
|
||||||
Param(ws.PathParameter("namespace", "kubernetes namespace")).
|
|
||||||
Param(ws.PathParameter("role", "role name")).
|
|
||||||
Returns(http.StatusOK, api.StatusOK, []policy.SimpleRule{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
|
||||||
ws.Route(ws.GET("/rulesmapping/clusterroles").
|
|
||||||
To(handler.ClusterRulesMapping).
|
|
||||||
Doc("Get the mapping relationships between cluster roles and policy rules.").
|
|
||||||
Returns(http.StatusOK, api.StatusOK, policy.ClusterRoleRuleMapping).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
|
||||||
ws.Route(ws.GET("/rulesmapping/roles").
|
|
||||||
To(handler.RulesMapping).
|
|
||||||
Doc("Get the mapping relationships between namespaced roles and policy rules.").
|
|
||||||
Returns(http.StatusOK, api.StatusOK, policy.RoleRuleMapping).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||||
|
|
||||||
ws.Route(ws.GET("/workspaces/{workspace}/roles").
|
ws.Route(ws.GET("/workspaces/{workspace}/roles").
|
||||||
@@ -153,23 +83,14 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer
|
|||||||
Doc("List all workspace roles.").
|
Doc("List all workspace roles.").
|
||||||
Param(ws.PathParameter("workspace", "workspace name")).
|
Param(ws.PathParameter("workspace", "workspace name")).
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||||
ws.Route(ws.GET("/workspaces/{workspace}/roles/{role}").
|
|
||||||
To(handler.DescribeWorkspaceRole).
|
|
||||||
Doc("Describe the workspace role.").
|
|
||||||
Param(ws.PathParameter("workspace", "workspace name")).
|
|
||||||
Param(ws.PathParameter("role", "workspace role name")).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
|
||||||
ws.Route(ws.GET("/workspaces/{workspace}/roles/{role}/rules").
|
|
||||||
To(handler.ListWorkspaceRoleRules).
|
|
||||||
Doc("List all policy rules of the specified workspace role.").
|
|
||||||
Param(ws.PathParameter("workspace", "workspace name")).
|
|
||||||
Param(ws.PathParameter("role", "workspace role name")).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
|
||||||
ws.Route(ws.GET("/workspaces/{workspace}/members").
|
ws.Route(ws.GET("/workspaces/{workspace}/members").
|
||||||
To(handler.ListWorkspaceUsers).
|
To(handler.ListWorkspaceUsers).
|
||||||
Doc("List all members in the specified workspace.").
|
Doc("List all members in the specified workspace.").
|
||||||
Param(ws.PathParameter("workspace", "workspace name")).
|
Param(ws.PathParameter("workspace", "workspace name")).
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||||
|
|
||||||
|
// TODO re-design
|
||||||
ws.Route(ws.POST("/workspaces/{workspace}/members").
|
ws.Route(ws.POST("/workspaces/{workspace}/members").
|
||||||
To(handler.InviteUser).
|
To(handler.InviteUser).
|
||||||
Doc("Invite a member to the specified workspace.").
|
Doc("Invite a member to the specified workspace.").
|
||||||
@@ -182,12 +103,7 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer
|
|||||||
Param(ws.PathParameter("member", "username")).
|
Param(ws.PathParameter("member", "username")).
|
||||||
Returns(http.StatusOK, api.StatusOK, errors.Error{}).
|
Returns(http.StatusOK, api.StatusOK, errors.Error{}).
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
||||||
ws.Route(ws.GET("/workspaces/{workspace}/members/{member}").
|
|
||||||
To(handler.DescribeWorkspaceUser).
|
|
||||||
Doc("Describe the specified user in the given workspace.").
|
|
||||||
Param(ws.PathParameter("workspace", "workspace name")).
|
|
||||||
Param(ws.PathParameter("member", "username")).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
|
|
||||||
c.Add(ws)
|
c.Add(ws)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
121
pkg/kapis/oauth/handler.go
Normal file
121
pkg/kapis/oauth/handler.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 The KubeSphere Authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* /
|
||||||
|
*/
|
||||||
|
|
||||||
|
package oauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/emicklei/go-restful"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/klog"
|
||||||
|
"kubesphere.io/kubesphere/pkg/api"
|
||||||
|
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type oauthHandler struct {
|
||||||
|
issuer token.Issuer
|
||||||
|
config oauth.Configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOAUTHHandler(issuer token.Issuer, config oauth.Configuration) *oauthHandler {
|
||||||
|
return &oauthHandler{issuer: issuer, config: config}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement webhook authentication interface
|
||||||
|
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
|
||||||
|
func (h *oauthHandler) TokenReviewHandler(req *restful.Request, resp *restful.Response) {
|
||||||
|
var tokenReview auth.TokenReview
|
||||||
|
|
||||||
|
err := req.ReadEntity(&tokenReview)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
api.HandleBadRequest(resp, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tokenReview.Validate(); err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
api.HandleBadRequest(resp, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, _, err := h.issuer.Verify(tokenReview.Spec.Token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorln(err)
|
||||||
|
api.HandleInternalError(resp, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
success := auth.TokenReview{APIVersion: tokenReview.APIVersion,
|
||||||
|
Kind: auth.KindTokenReview,
|
||||||
|
Status: &auth.Status{
|
||||||
|
Authenticated: true,
|
||||||
|
User: map[string]interface{}{"username": user.GetName(), "uid": user.GetUID()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.WriteEntity(success)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Response) {
|
||||||
|
user, ok := request.UserFrom(req.Request.Context())
|
||||||
|
clientId := req.QueryParameter("client_id")
|
||||||
|
responseType := req.QueryParameter("response_type")
|
||||||
|
|
||||||
|
conf, err := h.config.Load(clientId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||||
|
resp.WriteError(http.StatusUnauthorized, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseType != "token" {
|
||||||
|
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: response type %s is not supported", responseType))
|
||||||
|
resp.WriteError(http.StatusUnauthorized, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
err := apierrors.NewUnauthorized("Unauthorized")
|
||||||
|
resp.WriteError(http.StatusUnauthorized, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, clm, err := h.issuer.IssueTo(user)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
|
||||||
|
resp.WriteError(http.StatusUnauthorized, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectURL := fmt.Sprintf("%s?access_token=%s&token_type=Bearer", conf.RedirectURL, accessToken)
|
||||||
|
expiresIn := clm.ExpiresAt - clm.IssuedAt
|
||||||
|
if expiresIn > 0 {
|
||||||
|
redirectURL = fmt.Sprintf("%s&expires_in=%v", redirectURL, expiresIn)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(resp, req.Request, redirectURL, http.StatusFound)
|
||||||
|
}
|
||||||
63
pkg/kapis/oauth/register.go
Normal file
63
pkg/kapis/oauth/register.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 The KubeSphere Authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* /
|
||||||
|
*/
|
||||||
|
|
||||||
|
package oauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emicklei/go-restful"
|
||||||
|
restfulspec "github.com/emicklei/go-restful-openapi"
|
||||||
|
"kubesphere.io/kubesphere/pkg/api"
|
||||||
|
"kubesphere.io/kubesphere/pkg/api/auth"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
|
||||||
|
"kubesphere.io/kubesphere/pkg/constants"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddToContainer(c *restful.Container, issuer token.Issuer, configuration oauth.Configuration) error {
|
||||||
|
ws := &restful.WebService{}
|
||||||
|
ws.Path("/oauth").
|
||||||
|
Consumes(restful.MIME_JSON).
|
||||||
|
Produces(restful.MIME_JSON)
|
||||||
|
|
||||||
|
handler := newOAUTHHandler(issuer, configuration)
|
||||||
|
|
||||||
|
// Implement webhook authentication interface
|
||||||
|
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
|
||||||
|
ws.Route(ws.POST("/authenticate").
|
||||||
|
Doc("TokenReview attempts to authenticate a token to a known user. Note: TokenReview requests may be cached by the webhook token authenticator plugin in the kube-apiserver.").
|
||||||
|
Reads(auth.TokenReview{}).
|
||||||
|
To(handler.TokenReviewHandler).
|
||||||
|
Returns(http.StatusOK, api.StatusOK, auth.TokenReview{}).
|
||||||
|
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
|
||||||
|
|
||||||
|
// TODO Built-in oauth2 server (provider)
|
||||||
|
// web console use 'Resource Owner Password Credentials Grant' or 'Client Credentials Grant' request for an OAuth token
|
||||||
|
// https://tools.ietf.org/html/rfc6749#section-4.3
|
||||||
|
// https://tools.ietf.org/html/rfc6749#section-4.4
|
||||||
|
|
||||||
|
// curl -u admin:P@88w0rd 'http://ks-apiserver.kubesphere-system.svc/oauth/authorize?client_id=kubesphere-console-client&response_type=token' -v
|
||||||
|
ws.Route(ws.GET("/authorize").
|
||||||
|
To(handler.AuthorizeHandler))
|
||||||
|
//ws.Route(ws.POST("/token"))
|
||||||
|
//ws.Route(ws.POST("/callback/{callback}"))
|
||||||
|
|
||||||
|
c.Add(ws)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,14 +3,12 @@ package v1alpha2
|
|||||||
import (
|
import (
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
k8serr "k8s.io/apimachinery/pkg/api/errors"
|
k8serr "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/util/net"
|
|
||||||
"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"
|
||||||
"kubesphere.io/kubesphere/pkg/informers"
|
"kubesphere.io/kubesphere/pkg/informers"
|
||||||
"kubesphere.io/kubesphere/pkg/models/iam"
|
"kubesphere.io/kubesphere/pkg/models/iam/am"
|
||||||
"kubesphere.io/kubesphere/pkg/models/monitoring"
|
"kubesphere.io/kubesphere/pkg/models/monitoring"
|
||||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
|
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
|
||||||
"kubesphere.io/kubesphere/pkg/models/tenant"
|
"kubesphere.io/kubesphere/pkg/models/tenant"
|
||||||
@@ -18,39 +16,22 @@ import (
|
|||||||
"kubesphere.io/kubesphere/pkg/server/params"
|
"kubesphere.io/kubesphere/pkg/server/params"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
|
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
|
||||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type tenantHandler struct {
|
type tenantHandler struct {
|
||||||
tenant tenant.Interface
|
tenant tenant.Interface
|
||||||
am iam.AccessManagementInterface
|
am am.AccessManagementInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTenantHandler(k8sClient k8s.Client, factory informers.InformerFactory, db *mysql.Database) *tenantHandler {
|
func newTenantHandler(k8sClient k8s.Client, factory informers.InformerFactory, db *mysql.Database) *tenantHandler {
|
||||||
|
|
||||||
return &tenantHandler{
|
return &tenantHandler{
|
||||||
tenant: tenant.New(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory(), factory.KubeSphereSharedInformerFactory(), db),
|
tenant: tenant.New(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory(), factory.KubeSphereSharedInformerFactory(), db),
|
||||||
am: iam.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
|
am: am.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *tenantHandler) ListWorkspaceRules(req *restful.Request, resp *restful.Response) {
|
|
||||||
workspace := req.PathParameter("workspace")
|
|
||||||
username := req.HeaderParameter(constants.UserNameHeader)
|
|
||||||
|
|
||||||
rules, err := h.tenant.GetWorkspaceSimpleRules(workspace, username)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteEntity(rules)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *tenantHandler) ListWorkspaces(req *restful.Request, resp *restful.Response) {
|
func (h *tenantHandler) ListWorkspaces(req *restful.Request, resp *restful.Response) {
|
||||||
username := req.HeaderParameter(constants.UserNameHeader)
|
username := req.HeaderParameter(constants.UserNameHeader)
|
||||||
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
|
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
|
||||||
@@ -256,136 +237,3 @@ func (h *tenantHandler) DeleteDevopsProject(req *restful.Request, resp *restful.
|
|||||||
func (h *tenantHandler) CreateDevopsProject(req *restful.Request, resp *restful.Response) {
|
func (h *tenantHandler) CreateDevopsProject(req *restful.Request, resp *restful.Response) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *tenantHandler) ListNamespaceRules(req *restful.Request, resp *restful.Response) {
|
|
||||||
namespace := req.PathParameter("namespace")
|
|
||||||
username := req.HeaderParameter(constants.UserNameHeader)
|
|
||||||
|
|
||||||
rules, err := h.tenant.GetNamespaceSimpleRules(namespace, username)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteAsJson(rules)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *tenantHandler) ListDevopsRules(req *restful.Request, resp *restful.Response) {
|
|
||||||
|
|
||||||
devops := req.PathParameter("devops")
|
|
||||||
username := req.HeaderParameter(constants.UserNameHeader)
|
|
||||||
|
|
||||||
rules, err := h.tenant.GetUserDevopsSimpleRules(username, devops)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
api.HandleInternalError(resp, nil, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteAsJson(rules)
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO(wansir): We need move this part to logging module
|
|
||||||
//func (h *tenantHandler) LogQuery(req *restful.Request, resp *restful.Response) {
|
|
||||||
// operation := req.QueryParameter("operation")
|
|
||||||
// req, err := h.regenerateLoggingRequest(req)
|
|
||||||
// switch {
|
|
||||||
// case err != nil:
|
|
||||||
// api.HandleInternalError(resp, err)
|
|
||||||
// case req != nil:
|
|
||||||
// loggingv1alpha2.Get(req, loggingv1alpha2.LevelCluster, h.k8s, h.lo, resp)
|
|
||||||
// default:
|
|
||||||
// if operation == "export" {
|
|
||||||
// resp.Header().Set(restful.HEADER_ContentType, "text/plain")
|
|
||||||
// resp.Header().Set("Content-Disposition", "attachment")
|
|
||||||
// resp.Write(nil)
|
|
||||||
// } else {
|
|
||||||
// resp.WriteAsJson(v1alpha2.APIResponse{Logs: new(loggingclient.Logs)})
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
// override namespace query conditions
|
|
||||||
//TODO(wansir): We need move this part to logging module
|
|
||||||
func (h *tenantHandler) regenerateLoggingRequest(req *restful.Request) (*restful.Request, error) {
|
|
||||||
|
|
||||||
username := req.HeaderParameter(constants.UserNameHeader)
|
|
||||||
|
|
||||||
// regenerate the request for log query
|
|
||||||
newUrl := net.FormatURL("http", "127.0.0.1", 80, "/kapis/logging.kubesphere.io/v1alpha2/cluster")
|
|
||||||
values := req.Request.URL.Query()
|
|
||||||
|
|
||||||
clusterRoleRules, err := h.am.GetClusterPolicyRules(username)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hasClusterLogAccess := iam.RulesMatchesRequired(clusterRoleRules, rbacv1.PolicyRule{Verbs: []string{"get"}, Resources: []string{"*"}, APIGroups: []string{"logging.kubesphere.io"}})
|
|
||||||
// if the user is not a cluster admin
|
|
||||||
if !hasClusterLogAccess {
|
|
||||||
queryNamespaces := strings.Split(req.QueryParameter("namespaces"), ",")
|
|
||||||
// then the user can only view logs of namespaces he belongs to
|
|
||||||
namespaces := make([]string, 0)
|
|
||||||
roles, err := h.am.GetRoles("", username)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, role := range roles {
|
|
||||||
if !sliceutil.HasString(namespaces, role.Namespace) && iam.RulesMatchesRequired(role.Rules, rbacv1.PolicyRule{Verbs: []string{"get"}, Resources: []string{"*"}, APIGroups: []string{"logging.kubesphere.io"}}) {
|
|
||||||
namespaces = append(namespaces, role.Namespace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the user belongs to no namespace
|
|
||||||
// then no log visible
|
|
||||||
if len(namespaces) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
} else if len(queryNamespaces) == 1 && queryNamespaces[0] == "" {
|
|
||||||
values.Set("namespaces", strings.Join(namespaces, ","))
|
|
||||||
} else {
|
|
||||||
inter := intersection(queryNamespaces, namespaces)
|
|
||||||
if len(inter) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
values.Set("namespaces", strings.Join(inter, ","))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newUrl.RawQuery = values.Encode()
|
|
||||||
|
|
||||||
// forward the request to logging model
|
|
||||||
newHttpRequest, _ := http.NewRequest(http.MethodGet, newUrl.String(), nil)
|
|
||||||
return restful.NewRequest(newHttpRequest), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func intersection(s1, s2 []string) (inter []string) {
|
|
||||||
hash := make(map[string]bool)
|
|
||||||
for _, e := range s1 {
|
|
||||||
hash[e] = true
|
|
||||||
}
|
|
||||||
for _, e := range s2 {
|
|
||||||
// If elements present in the hashmap then append intersection list.
|
|
||||||
if hash[e] {
|
|
||||||
inter = append(inter, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Remove dups from slice.
|
|
||||||
inter = removeDups(inter)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//Remove dups from slice.
|
|
||||||
func removeDups(elements []string) (nodups []string) {
|
|
||||||
encountered := make(map[string]bool)
|
|
||||||
for _, element := range elements {
|
|
||||||
if !encountered[element] {
|
|
||||||
nodups = append(nodups, element)
|
|
||||||
encountered[element] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import (
|
|||||||
"kubesphere.io/kubesphere/pkg/constants"
|
"kubesphere.io/kubesphere/pkg/constants"
|
||||||
"kubesphere.io/kubesphere/pkg/informers"
|
"kubesphere.io/kubesphere/pkg/informers"
|
||||||
"kubesphere.io/kubesphere/pkg/models"
|
"kubesphere.io/kubesphere/pkg/models"
|
||||||
"kubesphere.io/kubesphere/pkg/models/iam/policy"
|
|
||||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||||
"kubesphere.io/kubesphere/pkg/server/params"
|
"kubesphere.io/kubesphere/pkg/server/params"
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
||||||
@@ -59,24 +58,6 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer
|
|||||||
Param(ws.PathParameter("workspace", "workspace name")).
|
Param(ws.PathParameter("workspace", "workspace name")).
|
||||||
Returns(http.StatusOK, api.StatusOK, v1alpha1.Workspace{}).
|
Returns(http.StatusOK, api.StatusOK, v1alpha1.Workspace{}).
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||||
ws.Route(ws.GET("/workspaces/{workspace}/rules").
|
|
||||||
To(handler.ListWorkspaceRules).
|
|
||||||
Param(ws.PathParameter("workspace", "workspace name")).
|
|
||||||
Doc("List the rules of the specified workspace for the current user").
|
|
||||||
Returns(http.StatusOK, api.StatusOK, policy.SimpleRule{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
|
||||||
ws.Route(ws.GET("/namespaces/{namespace}/rules").
|
|
||||||
To(handler.ListNamespaceRules).
|
|
||||||
Param(ws.PathParameter("namespace", "the name of the namespace")).
|
|
||||||
Doc("List the rules of the specified namespace for the current user").
|
|
||||||
Returns(http.StatusOK, api.StatusOK, policy.SimpleRule{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
|
||||||
ws.Route(ws.GET("/devops/{devops}/rules").
|
|
||||||
To(handler.ListDevopsRules).
|
|
||||||
Param(ws.PathParameter("devops", "devops project ID")).
|
|
||||||
Doc("List the rules of the specified DevOps project for the current user").
|
|
||||||
Returns(http.StatusOK, api.StatusOK, policy.SimpleRule{}).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
|
||||||
ws.Route(ws.GET("/workspaces/{workspace}/namespaces").
|
ws.Route(ws.GET("/workspaces/{workspace}/namespaces").
|
||||||
To(handler.ListNamespaces).
|
To(handler.ListNamespaces).
|
||||||
Param(ws.PathParameter("workspace", "workspace name")).
|
Param(ws.PathParameter("workspace", "workspace name")).
|
||||||
@@ -151,32 +132,6 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer
|
|||||||
Doc("Delete the specified devops project from the workspace").
|
Doc("Delete the specified devops project from the workspace").
|
||||||
Returns(http.StatusOK, api.StatusOK, devopsv1alpha2.DevOpsProject{}).
|
Returns(http.StatusOK, api.StatusOK, devopsv1alpha2.DevOpsProject{}).
|
||||||
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
|
||||||
//ws.Route(ws.GET("/logs").
|
|
||||||
// To(handler.LogQuery).
|
|
||||||
// Doc("Query cluster-level logs in a multi-tenants environment").
|
|
||||||
// Param(ws.QueryParameter("operation", "Operation type. This can be one of four types: query (for querying logs), statistics (for retrieving statistical data), histogram (for displaying log count by time interval) and export (for exporting logs). Defaults to query.").DefaultValue("query").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("workspaces", "A comma-separated list of workspaces. This field restricts the query to specified workspaces. For example, the following filter matches the workspace my-ws and demo-ws: `my-ws,demo-ws`").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("workspace_query", "A comma-separated list of keywords. Differing from **workspaces**, this field performs fuzzy matching on workspaces. For example, the following value limits the query to workspaces whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("namespaces", "A comma-separated list of namespaces. This field restricts the query to specified namespaces. For example, the following filter matches the namespace my-ns and demo-ns: `my-ns,demo-ns`").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("namespace_query", "A comma-separated list of keywords. Differing from **namespaces**, this field performs fuzzy matching on namespaces. For example, the following value limits the query to namespaces whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("workloads", "A comma-separated list of workloads. This field restricts the query to specified workloads. For example, the following filter matches the workload my-wl and demo-wl: `my-wl,demo-wl`").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("workload_query", "A comma-separated list of keywords. Differing from **workloads**, this field performs fuzzy matching on workloads. For example, the following value limits the query to workloads whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("pods", "A comma-separated list of pods. This field restricts the query to specified pods. For example, the following filter matches the pod my-po and demo-po: `my-po,demo-po`").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("pod_query", "A comma-separated list of keywords. Differing from **pods**, this field performs fuzzy matching on pods. For example, the following value limits the query to pods whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("containers", "A comma-separated list of containers. This field restricts the query to specified containers. For example, the following filter matches the container my-cont and demo-cont: `my-cont,demo-cont`").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("container_query", "A comma-separated list of keywords. Differing from **containers**, this field performs fuzzy matching on containers. For example, the following value limits the query to containers whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("log_query", "A comma-separated list of keywords. The query returns logs which contain at least one keyword. Case-insensitive matching. For example, if the field is set to `err,INFO`, the query returns any log containing err(ERR,Err,...) *OR* INFO(info,InFo,...).").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("interval", "Time interval. It requires **operation** is set to histogram. The format is [0-9]+[smhdwMqy]. Defaults to 15m (i.e. 15 min).").DefaultValue("15m").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("start_time", "Start time of query. Default to 0. The format is a string representing milliseconds since the epoch, eg. 1559664000000.").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("end_time", "End time of query. Default to now. The format is a string representing milliseconds since the epoch, eg. 1559664000000.").DataType("string").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("sort", "Sort order. One of acs, desc. This field sorts logs by timestamp.").DataType("string").DefaultValue("desc").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("from", "The offset from the result set. This field returns query results from the specified offset. It requires **operation** is set to query. Defaults to 0 (i.e. from the beginning of the result set).").DataType("integer").DefaultValue("0").Required(false)).
|
|
||||||
// Param(ws.QueryParameter("size", "Size of result to return. It requires **operation** is set to query. Defaults to 10 (i.e. 10 log records).").DataType("integer").DefaultValue("10").Required(false)).
|
|
||||||
// Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}).
|
|
||||||
// Writes(v1alpha2.Response{}).
|
|
||||||
// Returns(http.StatusOK, api.StatusOK, v1alpha2.Response{})).
|
|
||||||
// Consumes(restful.MIME_JSON, restful.MIME_XML).
|
|
||||||
// Produces(restful.MIME_JSON, "text/plain")
|
|
||||||
|
|
||||||
c.Add(ws)
|
c.Add(ws)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,606 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright 2019 The KubeSphere Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package iam
|
|
||||||
|
|
||||||
import (
|
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/client-go/informers"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/klog"
|
|
||||||
"kubesphere.io/kubesphere/pkg/constants"
|
|
||||||
"kubesphere.io/kubesphere/pkg/models"
|
|
||||||
"kubesphere.io/kubesphere/pkg/models/iam/policy"
|
|
||||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
|
|
||||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/clusterrole"
|
|
||||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
|
|
||||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/role"
|
|
||||||
"kubesphere.io/kubesphere/pkg/server/params"
|
|
||||||
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ClusterRoleKind = "ClusterRole"
|
|
||||||
NamespaceAdminRoleBindName = "admin"
|
|
||||||
NamespaceViewerRoleBindName = "viewer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AccessManagementInterface interface {
|
|
||||||
GetClusterRole(username string) (*rbacv1.ClusterRole, error)
|
|
||||||
UnBindAllRoles(username string) error
|
|
||||||
ListRoleBindings(namespace string, role string) ([]*rbacv1.RoleBinding, error)
|
|
||||||
CreateClusterRoleBinding(username string, clusterRole string) error
|
|
||||||
ListRoles(namespace string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error)
|
|
||||||
ListClusterRoles(conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error)
|
|
||||||
ListClusterRoleBindings(clusterRole string) ([]*rbacv1.ClusterRoleBinding, error)
|
|
||||||
GetClusterRoleSimpleRules(clusterRole string) ([]policy.SimpleRule, error)
|
|
||||||
GetRoleSimpleRules(namespace string, role string) ([]policy.SimpleRule, error)
|
|
||||||
GetRoles(namespace, username string) ([]*rbacv1.Role, error)
|
|
||||||
GetClusterPolicyRules(username string) ([]rbacv1.PolicyRule, error)
|
|
||||||
GetPolicyRules(namespace, username string) ([]rbacv1.PolicyRule, error)
|
|
||||||
GetWorkspaceRoleSimpleRules(workspace, roleName string) []policy.SimpleRule
|
|
||||||
GetWorkspaceRole(workspace, username string) (*rbacv1.ClusterRole, error)
|
|
||||||
GetWorkspaceRoleMap(username string) (map[string]string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type amOperator struct {
|
|
||||||
informers informers.SharedInformerFactory
|
|
||||||
resources resource.ResourceGetter
|
|
||||||
kubeClient kubernetes.Interface
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) ListClusterRoleBindings(clusterRole string) ([]*rbacv1.ClusterRoleBinding, error) {
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetRoles(namespace, username string) ([]*rbacv1.Role, error) {
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetClusterPolicyRules(username string) ([]rbacv1.PolicyRule, error) {
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetPolicyRules(namespace, username string) ([]rbacv1.PolicyRule, error) {
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetWorkspaceRole(workspace, username string) (*rbacv1.ClusterRole, error) {
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) UnBindAllRoles(username string) error {
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAMOperator(kubeClient kubernetes.Interface, informers informers.SharedInformerFactory) *amOperator {
|
|
||||||
resourceGetter := resource.ResourceGetter{}
|
|
||||||
resourceGetter.Add(v1alpha2.Role, role.NewRoleSearcher(informers))
|
|
||||||
resourceGetter.Add(v1alpha2.ClusterRoles, clusterrole.NewClusterRoleSearcher(informers))
|
|
||||||
return &amOperator{
|
|
||||||
informers: informers,
|
|
||||||
resources: resourceGetter,
|
|
||||||
kubeClient: kubeClient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetDevopsRoleSimpleRules(role string) []policy.SimpleRule {
|
|
||||||
var rules []policy.SimpleRule
|
|
||||||
|
|
||||||
switch role {
|
|
||||||
case "developer":
|
|
||||||
rules = []policy.SimpleRule{
|
|
||||||
{Name: "pipelines", Actions: []string{"view", "trigger"}},
|
|
||||||
{Name: "roles", Actions: []string{"view"}},
|
|
||||||
{Name: "members", Actions: []string{"view"}},
|
|
||||||
{Name: "devops", Actions: []string{"view"}},
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "owner":
|
|
||||||
rules = []policy.SimpleRule{
|
|
||||||
{Name: "pipelines", Actions: []string{"create", "edit", "view", "delete", "trigger"}},
|
|
||||||
{Name: "roles", Actions: []string{"view"}},
|
|
||||||
{Name: "members", Actions: []string{"create", "edit", "view", "delete"}},
|
|
||||||
{Name: "credentials", Actions: []string{"create", "edit", "view", "delete"}},
|
|
||||||
{Name: "devops", Actions: []string{"edit", "view", "delete"}},
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "maintainer":
|
|
||||||
rules = []policy.SimpleRule{
|
|
||||||
{Name: "pipelines", Actions: []string{"create", "edit", "view", "delete", "trigger"}},
|
|
||||||
{Name: "roles", Actions: []string{"view"}},
|
|
||||||
{Name: "members", Actions: []string{"view"}},
|
|
||||||
{Name: "credentials", Actions: []string{"create", "edit", "view", "delete"}},
|
|
||||||
{Name: "devops", Actions: []string{"view"}},
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "reporter":
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
rules = []policy.SimpleRule{
|
|
||||||
{Name: "pipelines", Actions: []string{"view"}},
|
|
||||||
{Name: "roles", Actions: []string{"view"}},
|
|
||||||
{Name: "members", Actions: []string{"view"}},
|
|
||||||
{Name: "devops", Actions: []string{"view"}},
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return rules
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user roles in namespace
|
|
||||||
func (am *amOperator) GetUserRoles(namespace, username string) ([]*rbacv1.Role, error) {
|
|
||||||
clusterRoleLister := am.informers.Rbac().V1().ClusterRoles().Lister()
|
|
||||||
roleBindingLister := am.informers.Rbac().V1().RoleBindings().Lister()
|
|
||||||
roleLister := am.informers.Rbac().V1().Roles().Lister()
|
|
||||||
roleBindings, err := roleBindingLister.RoleBindings(namespace).List(labels.Everything())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
roles := make([]*rbacv1.Role, 0)
|
|
||||||
|
|
||||||
for _, roleBinding := range roleBindings {
|
|
||||||
if ContainsUser(roleBinding.Subjects, username) {
|
|
||||||
if roleBinding.RoleRef.Kind == ClusterRoleKind {
|
|
||||||
clusterRole, err := clusterRoleLister.Get(roleBinding.RoleRef.Name)
|
|
||||||
if err != nil {
|
|
||||||
if apierrors.IsNotFound(err) {
|
|
||||||
klog.Warningf("cluster role %s not found but bind user %s in namespace %s", roleBinding.RoleRef.Name, username, namespace)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
role := rbacv1.Role{}
|
|
||||||
role.TypeMeta = clusterRole.TypeMeta
|
|
||||||
role.ObjectMeta = clusterRole.ObjectMeta
|
|
||||||
role.Rules = clusterRole.Rules
|
|
||||||
role.Namespace = roleBinding.Namespace
|
|
||||||
roles = append(roles, &role)
|
|
||||||
} else {
|
|
||||||
role, err := roleLister.Roles(roleBinding.Namespace).Get(roleBinding.RoleRef.Name)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if apierrors.IsNotFound(err) {
|
|
||||||
klog.Warningf("namespace %s role %s not found, but bind user %s", namespace, roleBinding.RoleRef.Name, username)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
roles = append(roles, role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return roles, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetUserClusterRoles(username string) (*rbacv1.ClusterRole, []*rbacv1.ClusterRole, error) {
|
|
||||||
clusterRoleLister := am.informers.Rbac().V1().ClusterRoles().Lister()
|
|
||||||
clusterRoleBindingLister := am.informers.Rbac().V1().ClusterRoleBindings().Lister()
|
|
||||||
clusterRoleBindings, err := clusterRoleBindingLister.List(labels.Everything())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
clusterRoles := make([]*rbacv1.ClusterRole, 0)
|
|
||||||
userFacingClusterRole := &rbacv1.ClusterRole{}
|
|
||||||
for _, clusterRoleBinding := range clusterRoleBindings {
|
|
||||||
if ContainsUser(clusterRoleBinding.Subjects, username) {
|
|
||||||
clusterRole, err := clusterRoleLister.Get(clusterRoleBinding.RoleRef.Name)
|
|
||||||
if err != nil {
|
|
||||||
if apierrors.IsNotFound(err) {
|
|
||||||
klog.Warningf("cluster role %s not found but bind user %s", clusterRoleBinding.RoleRef.Name, username)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if clusterRoleBinding.Name == username {
|
|
||||||
userFacingClusterRole = clusterRole
|
|
||||||
}
|
|
||||||
clusterRoles = append(clusterRoles, clusterRole)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return userFacingClusterRole, clusterRoles, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetClusterRole(username string) (*rbacv1.ClusterRole, error) {
|
|
||||||
userFacingClusterRole, _, err := am.GetUserClusterRoles(username)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return userFacingClusterRole, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetUserClusterRules(username string) ([]rbacv1.PolicyRule, error) {
|
|
||||||
_, clusterRoles, err := am.GetUserClusterRoles(username)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := make([]rbacv1.PolicyRule, 0)
|
|
||||||
for _, clusterRole := range clusterRoles {
|
|
||||||
rules = append(rules, clusterRole.Rules...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rules, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetUserRules(namespace, username string) ([]rbacv1.PolicyRule, error) {
|
|
||||||
roles, err := am.GetUserRoles(namespace, username)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := make([]rbacv1.PolicyRule, 0)
|
|
||||||
for _, role := range roles {
|
|
||||||
rules = append(rules, role.Rules...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rules, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetWorkspaceRoleBindings(workspace string) ([]*rbacv1.ClusterRoleBinding, error) {
|
|
||||||
|
|
||||||
clusterRoleBindings, err := am.informers.Rbac().V1().ClusterRoleBindings().Lister().List(labels.Everything())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln("get cluster role bindings", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]*rbacv1.ClusterRoleBinding, 0)
|
|
||||||
|
|
||||||
for _, roleBinding := range clusterRoleBindings {
|
|
||||||
if k8sutil.IsControlledBy(roleBinding.OwnerReferences, "Workspace", workspace) {
|
|
||||||
result = append(result, roleBinding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (am *amOperator) GetWorkspaceRole(workspace, role string) (*rbacv1.ClusterRole, error) {
|
|
||||||
// if !sliceutil.HasString(constants.WorkSpaceRoles, role) {
|
|
||||||
// return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "workspace role"}, role)
|
|
||||||
// }
|
|
||||||
// role = fmt.Sprintf("workspace:%s:%s", workspace, strings.TrimPrefix(role, "workspace-"))
|
|
||||||
// return am.informers.Rbac().V1().ClusterRoles().Lister().Get(role)
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (am *amOperator) GetWorkspaceRoleMap(username string) (map[string]string, error) {
|
|
||||||
|
|
||||||
clusterRoleBindings, err := am.informers.Rbac().V1().ClusterRoleBindings().Lister().List(labels.Everything())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln("get cluster role bindings", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(map[string]string, 0)
|
|
||||||
|
|
||||||
for _, roleBinding := range clusterRoleBindings {
|
|
||||||
if workspace := k8sutil.GetControlledWorkspace(roleBinding.OwnerReferences); workspace != "" &&
|
|
||||||
ContainsUser(roleBinding.Subjects, username) {
|
|
||||||
result[workspace] = roleBinding.RoleRef.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetUserWorkspaceRole(workspace, username string) (*rbacv1.ClusterRole, error) {
|
|
||||||
workspaceRoleMap, err := am.GetWorkspaceRoleMap(username)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if workspaceRole := workspaceRoleMap[workspace]; workspaceRole != "" {
|
|
||||||
return am.informers.Rbac().V1().ClusterRoles().Lister().Get(workspaceRole)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "workspace user"}, username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetRoleBindings(namespace string, roleName string) ([]*rbacv1.RoleBinding, error) {
|
|
||||||
roleBindingLister := am.informers.Rbac().V1().RoleBindings().Lister()
|
|
||||||
roleBindings, err := roleBindingLister.RoleBindings(namespace).List(labels.Everything())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
items := make([]*rbacv1.RoleBinding, 0)
|
|
||||||
|
|
||||||
for _, roleBinding := range roleBindings {
|
|
||||||
if roleName == "" {
|
|
||||||
items = append(items, roleBinding)
|
|
||||||
} else if roleBinding.RoleRef.Name == roleName {
|
|
||||||
items = append(items, roleBinding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetClusterRoleBindings(clusterRoleName string) ([]*rbacv1.ClusterRoleBinding, error) {
|
|
||||||
clusterRoleBindingLister := am.informers.Rbac().V1().ClusterRoleBindings().Lister()
|
|
||||||
roleBindings, err := clusterRoleBindingLister.List(labels.Everything())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
items := make([]*rbacv1.ClusterRoleBinding, 0)
|
|
||||||
|
|
||||||
for _, roleBinding := range roleBindings {
|
|
||||||
if roleBinding.RoleRef.Name == clusterRoleName {
|
|
||||||
items = append(items, roleBinding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) ListRoles(namespace string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
|
||||||
return am.resources.ListResources(namespace, v1alpha2.Roles, conditions, orderBy, reverse, limit, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) ListRoleBindings(namespace string, role string) ([]*rbacv1.RoleBinding, error) {
|
|
||||||
rbs, err := am.informers.Rbac().V1().RoleBindings().Lister().RoleBindings(namespace).List(labels.Everything())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]*rbacv1.RoleBinding, 0)
|
|
||||||
for _, rb := range rbs {
|
|
||||||
if rb.RoleRef.Name == role {
|
|
||||||
result = append(result, rb.DeepCopy())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) ListWorkspaceRoles(workspace string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
|
||||||
conditions.Match[v1alpha2.OwnerName] = workspace
|
|
||||||
conditions.Match[v1alpha2.OwnerKind] = "Workspace"
|
|
||||||
result, err := am.resources.ListResources("", v1alpha2.ClusterRoles, conditions, orderBy, reverse, limit, offset)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, item := range result.Items {
|
|
||||||
if role, ok := item.(*rbacv1.ClusterRole); ok {
|
|
||||||
role = role.DeepCopy()
|
|
||||||
role.Name = role.Annotations[constants.DisplayNameAnnotationKey]
|
|
||||||
result.Items[i] = role
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) ListClusterRoles(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
|
||||||
return am.resources.ListResources("", v1alpha2.ClusterRoles, conditions, orderBy, reverse, limit, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetWorkspaceRoleSimpleRules(workspace, roleName string) []policy.SimpleRule {
|
|
||||||
|
|
||||||
workspaceRules := make([]policy.SimpleRule, 0)
|
|
||||||
|
|
||||||
switch roleName {
|
|
||||||
case constants.WorkspaceAdmin:
|
|
||||||
workspaceRules = []policy.SimpleRule{
|
|
||||||
{Name: "workspaces", Actions: []string{"edit", "delete", "view"}},
|
|
||||||
{Name: "members", Actions: []string{"edit", "delete", "create", "view"}},
|
|
||||||
{Name: "devops", Actions: []string{"edit", "delete", "create", "view"}},
|
|
||||||
{Name: "projects", Actions: []string{"edit", "delete", "create", "view"}},
|
|
||||||
{Name: "roles", Actions: []string{"view"}},
|
|
||||||
{Name: "apps", Actions: []string{"view", "create", "manage"}},
|
|
||||||
{Name: "repos", Actions: []string{"view", "manage"}},
|
|
||||||
}
|
|
||||||
case constants.WorkspaceRegular:
|
|
||||||
workspaceRules = []policy.SimpleRule{
|
|
||||||
{Name: "members", Actions: []string{"view"}},
|
|
||||||
{Name: "devops", Actions: []string{"view", "create"}},
|
|
||||||
{Name: "projects", Actions: []string{"view", "create"}},
|
|
||||||
{Name: "apps", Actions: []string{"view", "create"}},
|
|
||||||
{Name: "repos", Actions: []string{"view"}},
|
|
||||||
}
|
|
||||||
case constants.WorkspaceViewer:
|
|
||||||
workspaceRules = []policy.SimpleRule{
|
|
||||||
{Name: "workspaces", Actions: []string{"view"}},
|
|
||||||
{Name: "members", Actions: []string{"view"}},
|
|
||||||
{Name: "devops", Actions: []string{"view"}},
|
|
||||||
{Name: "projects", Actions: []string{"view"}},
|
|
||||||
{Name: "roles", Actions: []string{"view"}},
|
|
||||||
{Name: "apps", Actions: []string{"view"}},
|
|
||||||
{Name: "repos", Actions: []string{"view"}},
|
|
||||||
}
|
|
||||||
case constants.WorkspacesManager:
|
|
||||||
workspaceRules = []policy.SimpleRule{
|
|
||||||
{Name: "workspaces", Actions: []string{"edit", "delete", "view"}},
|
|
||||||
{Name: "members", Actions: []string{"edit", "delete", "create", "view"}},
|
|
||||||
{Name: "roles", Actions: []string{"view"}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return workspaceRules
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert cluster role to rules
|
|
||||||
func (am *amOperator) GetClusterRoleSimpleRules(clusterRoleName string) ([]policy.SimpleRule, error) {
|
|
||||||
|
|
||||||
clusterRoleLister := am.informers.Rbac().V1().ClusterRoles().Lister()
|
|
||||||
clusterRole, err := clusterRoleLister.Get(clusterRoleName)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return getClusterSimpleRule(clusterRole.Rules), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) GetUserClusterSimpleRules(username string) ([]policy.SimpleRule, error) {
|
|
||||||
clusterRules, err := am.GetUserClusterRules(username)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return getClusterSimpleRule(clusterRules), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert roles to rules
|
|
||||||
func (am *amOperator) GetRoleSimpleRules(namespace string, roleName string) ([]policy.SimpleRule, error) {
|
|
||||||
|
|
||||||
roleLister := am.informers.Rbac().V1().Roles().Lister()
|
|
||||||
role, err := roleLister.Roles(namespace).Get(roleName)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConvertToSimpleRule(role.Rules), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getClusterSimpleRule(policyRules []rbacv1.PolicyRule) []policy.SimpleRule {
|
|
||||||
rules := make([]policy.SimpleRule, 0)
|
|
||||||
|
|
||||||
for i := 0; i < len(policy.ClusterRoleRuleMapping); i++ {
|
|
||||||
validActions := make([]string, 0)
|
|
||||||
for j := 0; j < (len(policy.ClusterRoleRuleMapping[i].Actions)); j++ {
|
|
||||||
if rulesMatchesAction(policyRules, policy.ClusterRoleRuleMapping[i].Actions[j]) {
|
|
||||||
validActions = append(validActions, policy.ClusterRoleRuleMapping[i].Actions[j].Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(validActions) > 0 {
|
|
||||||
rules = append(rules, policy.SimpleRule{Name: policy.ClusterRoleRuleMapping[i].Name, Actions: validActions})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rules
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConvertToSimpleRule(policyRules []rbacv1.PolicyRule) []policy.SimpleRule {
|
|
||||||
simpleRules := make([]policy.SimpleRule, 0)
|
|
||||||
for i := 0; i < len(policy.RoleRuleMapping); i++ {
|
|
||||||
rule := policy.SimpleRule{Name: policy.RoleRuleMapping[i].Name}
|
|
||||||
rule.Actions = make([]string, 0)
|
|
||||||
for j := 0; j < len(policy.RoleRuleMapping[i].Actions); j++ {
|
|
||||||
if rulesMatchesAction(policyRules, policy.RoleRuleMapping[i].Actions[j]) {
|
|
||||||
rule.Actions = append(rule.Actions, policy.RoleRuleMapping[i].Actions[j].Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(rule.Actions) > 0 {
|
|
||||||
simpleRules = append(simpleRules, rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return simpleRules
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *amOperator) CreateClusterRoleBinding(username string, clusterRoleName string) error {
|
|
||||||
clusterRoleLister := am.informers.Rbac().V1().ClusterRoles().Lister()
|
|
||||||
|
|
||||||
_, err := clusterRoleLister.Get(clusterRoleName)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO move to user controller
|
|
||||||
if clusterRoleName == constants.ClusterAdmin {
|
|
||||||
// create kubectl pod if cluster role is cluster-admin
|
|
||||||
//if err := kubectl.CreateKubectlDeploy(username); err != nil {
|
|
||||||
// klog.Error("create user terminal pod failed", username, err)
|
|
||||||
//}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// delete kubectl pod if cluster role is not cluster-admin, whether it exists or not
|
|
||||||
//if err := kubectl.DelKubectlDeploy(username); err != nil {
|
|
||||||
// klog.Error("delete user terminal pod failed", username, err)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
clusterRoleBinding := &rbacv1.ClusterRoleBinding{}
|
|
||||||
clusterRoleBinding.Name = username
|
|
||||||
clusterRoleBinding.RoleRef = rbacv1.RoleRef{Name: clusterRoleName, Kind: ClusterRoleKind}
|
|
||||||
clusterRoleBinding.Subjects = []rbacv1.Subject{{Kind: rbacv1.UserKind, Name: username}}
|
|
||||||
|
|
||||||
clusterRoleBindingLister := am.informers.Rbac().V1().ClusterRoleBindings().Lister()
|
|
||||||
found, err := clusterRoleBindingLister.Get(username)
|
|
||||||
|
|
||||||
if apierrors.IsNotFound(err) {
|
|
||||||
_, err = am.kubeClient.RbacV1().ClusterRoleBindings().Create(clusterRoleBinding)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln("create cluster role binding", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// cluster role changed
|
|
||||||
if found.RoleRef.Name != clusterRoleName {
|
|
||||||
deletePolicy := metav1.DeletePropagationBackground
|
|
||||||
gracePeriodSeconds := int64(0)
|
|
||||||
deleteOption := &metav1.DeleteOptions{PropagationPolicy: &deletePolicy, GracePeriodSeconds: &gracePeriodSeconds}
|
|
||||||
err = am.kubeClient.RbacV1().ClusterRoleBindings().Delete(found.Name, deleteOption)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = am.kubeClient.RbacV1().ClusterRoleBindings().Create(clusterRoleBinding)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ContainsUser(found.Subjects, username) {
|
|
||||||
found.Subjects = clusterRoleBinding.Subjects
|
|
||||||
_, err = am.kubeClient.RbacV1().ClusterRoleBindings().Update(found)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorln("update cluster role binding", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
78
pkg/models/iam/am/am.go
Normal file
78
pkg/models/iam/am/am.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 am
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
|
||||||
|
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/clusterrole"
|
||||||
|
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
|
||||||
|
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/role"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ClusterRoleKind = "ClusterRole"
|
||||||
|
NamespaceAdminRoleBindName = "admin"
|
||||||
|
NamespaceViewerRoleBindName = "viewer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccessManagementInterface interface {
|
||||||
|
GetPlatformRole(username string) (Role, error)
|
||||||
|
GetClusterRole(cluster, username string) (Role, error)
|
||||||
|
GetWorkspaceRole(workspace, username string) (Role, error)
|
||||||
|
GetNamespaceRole(cluster, namespace, username string) (Role, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Role interface {
|
||||||
|
GetName() string
|
||||||
|
GetRego() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type amOperator struct {
|
||||||
|
informers informers.SharedInformerFactory
|
||||||
|
resources resource.ResourceGetter
|
||||||
|
kubeClient kubernetes.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAMOperator(kubeClient kubernetes.Interface, informers informers.SharedInformerFactory) AccessManagementInterface {
|
||||||
|
resourceGetter := resource.ResourceGetter{}
|
||||||
|
resourceGetter.Add(v1alpha2.Role, role.NewRoleSearcher(informers))
|
||||||
|
resourceGetter.Add(v1alpha2.ClusterRoles, clusterrole.NewClusterRoleSearcher(informers))
|
||||||
|
return &amOperator{
|
||||||
|
informers: informers,
|
||||||
|
resources: resourceGetter,
|
||||||
|
kubeClient: kubeClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *amOperator) GetPlatformRole(username string) (Role, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *amOperator) GetClusterRole(cluster, username string) (Role, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *amOperator) GetWorkspaceRole(workspace, username string) (Role, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *amOperator) GetNamespaceRole(cluster, namespace, username string) (Role, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
139
pkg/models/iam/am/fake_operator.go
Normal file
139
pkg/models/iam/am/fake_operator.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 am
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeRole struct {
|
||||||
|
Name string
|
||||||
|
Rego string
|
||||||
|
}
|
||||||
|
type FakeOperator struct {
|
||||||
|
cache cache.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeOperator) queryFakeRole(cacheKey string) (Role, error) {
|
||||||
|
data, err := f.cache.Get(cacheKey)
|
||||||
|
if err != nil {
|
||||||
|
if err == cache.ErrNoSuchKey {
|
||||||
|
return &FakeRole{
|
||||||
|
Name: "DenyAll",
|
||||||
|
Rego: "package authz\ndefault allow = false",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var role FakeRole
|
||||||
|
err = json.Unmarshal([]byte(data), &role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return role, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeOperator) saveFakeRole(cacheKey string, role FakeRole) error {
|
||||||
|
data, err := json.Marshal(role)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return f.cache.Set(cacheKey, string(data), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeOperator) GetPlatformRole(username string) (Role, error) {
|
||||||
|
return f.queryFakeRole(platformRoleCacheKey(username))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeOperator) GetClusterRole(cluster, username string) (Role, error) {
|
||||||
|
return f.queryFakeRole(clusterRoleCacheKey(cluster, username))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeOperator) GetWorkspaceRole(workspace, username string) (Role, error) {
|
||||||
|
return f.queryFakeRole(workspaceRoleCacheKey(workspace, username))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeOperator) GetNamespaceRole(cluster, namespace, username string) (Role, error) {
|
||||||
|
return f.queryFakeRole(namespaceRoleCacheKey(cluster, namespace, username))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeOperator) Prepare(platformRoles map[string]FakeRole, clusterRoles map[string]map[string]FakeRole, workspaceRoles map[string]map[string]FakeRole, namespaceRoles map[string]map[string]map[string]FakeRole) {
|
||||||
|
|
||||||
|
for username, role := range platformRoles {
|
||||||
|
f.saveFakeRole(platformRoleCacheKey(username), role)
|
||||||
|
}
|
||||||
|
for cluster, roles := range clusterRoles {
|
||||||
|
for username, role := range roles {
|
||||||
|
f.saveFakeRole(clusterRoleCacheKey(cluster, username), role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for workspace, roles := range workspaceRoles {
|
||||||
|
for username, role := range roles {
|
||||||
|
f.saveFakeRole(workspaceRoleCacheKey(workspace, username), role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for cluster, nsRoles := range namespaceRoles {
|
||||||
|
for namespace, roles := range nsRoles {
|
||||||
|
for username, role := range roles {
|
||||||
|
f.saveFakeRole(namespaceRoleCacheKey(cluster, namespace, username), role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func namespaceRoleCacheKey(cluster, namespace, username string) string {
|
||||||
|
return fmt.Sprintf("cluster.%s.namespaces.%s.roles.%s", cluster, namespace, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clusterRoleCacheKey(cluster, username string) string {
|
||||||
|
return fmt.Sprintf("cluster.%s.roles.%s", cluster, username)
|
||||||
|
}
|
||||||
|
func workspaceRoleCacheKey(workspace, username string) string {
|
||||||
|
return fmt.Sprintf("workspace.%s.roles.%s", workspace, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func platformRoleCacheKey(username string) string {
|
||||||
|
return fmt.Sprintf("platform.roles.%s", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeRole) GetName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FakeRole) GetRego() string {
|
||||||
|
return f.Rego
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeAMOperator() *FakeOperator {
|
||||||
|
operator := &FakeOperator{cache: cache.NewSimpleCache()}
|
||||||
|
operator.saveFakeRole(platformRoleCacheKey("admin"), FakeRole{
|
||||||
|
Name: "admin",
|
||||||
|
Rego: "package authz\ndefault allow = true",
|
||||||
|
})
|
||||||
|
operator.saveFakeRole(platformRoleCacheKey(user.Anonymous), FakeRole{
|
||||||
|
Name: "admin",
|
||||||
|
Rego: "package authz\ndefault allow = false",
|
||||||
|
})
|
||||||
|
return operator
|
||||||
|
}
|
||||||
@@ -1,220 +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 iam
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
"k8s.io/klog"
|
|
||||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
|
||||||
"kubesphere.io/kubesphere/pkg/api/iam/token"
|
|
||||||
"kubesphere.io/kubesphere/pkg/models"
|
|
||||||
"kubesphere.io/kubesphere/pkg/server/params"
|
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
|
||||||
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IdentityManagementInterface interface {
|
|
||||||
CreateUser(user *iam.User) (*iam.User, error)
|
|
||||||
DeleteUser(username string) error
|
|
||||||
DescribeUser(username string) (*iam.User, error)
|
|
||||||
Login(username, password, ip string) (*oauth2.Token, error)
|
|
||||||
ModifyUser(user *iam.User) (*iam.User, error)
|
|
||||||
ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
|
||||||
GetUserRoles(username string) ([]*rbacv1.Role, error)
|
|
||||||
GetUserRole(namespace string, username string) (*rbacv1.Role, error)
|
|
||||||
VerifyToken(token string) (*iam.User, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type imOperator struct {
|
|
||||||
authenticateOptions *iam.AuthenticationOptions
|
|
||||||
ldapClient ldap.Interface
|
|
||||||
cacheClient cache.Interface
|
|
||||||
issuer token.Issuer
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
AuthRateLimitExceeded = errors.New("user auth rate limit exceeded")
|
|
||||||
UserAlreadyExists = errors.New("user already exists")
|
|
||||||
UserNotExists = errors.New("user not exists")
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewIMOperator(ldapClient ldap.Interface, cacheClient cache.Interface, options *iam.AuthenticationOptions) *imOperator {
|
|
||||||
return &imOperator{
|
|
||||||
ldapClient: ldapClient,
|
|
||||||
cacheClient: cacheClient,
|
|
||||||
authenticateOptions: options,
|
|
||||||
issuer: token.NewJwtTokenIssuer(token.DefaultIssuerName, []byte(options.JwtSecret)),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) ModifyUser(user *iam.User) (*iam.User, error) {
|
|
||||||
err := im.ldapClient.Update(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear auth failed record
|
|
||||||
if user.Password != "" {
|
|
||||||
records, err := im.cacheClient.Keys(authenticationFailedKeyForUsername(user.Username, "*"))
|
|
||||||
if err == nil {
|
|
||||||
im.cacheClient.Del(records...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return im.ldapClient.Get(user.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error) {
|
|
||||||
|
|
||||||
records, err := im.cacheClient.Keys(authenticationFailedKeyForUsername(username, "*"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(records) > im.authenticateOptions.MaxAuthenticateRetries {
|
|
||||||
return nil, AuthRateLimitExceeded
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := im.ldapClient.Get(username)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = im.ldapClient.Verify(user.Username, password)
|
|
||||||
if err != nil {
|
|
||||||
if err == ldap.ErrInvalidCredentials {
|
|
||||||
im.cacheClient.Set(authenticationFailedKeyForUsername(username, fmt.Sprintf("%d", time.Now().UnixNano())), "", 30*time.Minute)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
issuedToken, err := im.issuer.IssueTo(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: I think we should come up with a better strategy to prevent multiple login.
|
|
||||||
tokenKey := tokenKeyForUsername(user.Username, issuedToken)
|
|
||||||
if !im.authenticateOptions.MultipleLogin {
|
|
||||||
// multi login not allowed, remove the previous token
|
|
||||||
sessions, err := im.cacheClient.Keys(tokenKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sessions) > 0 {
|
|
||||||
klog.V(4).Infoln("revoke token", sessions)
|
|
||||||
err = im.cacheClient.Del(sessions...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// save token with expiration time
|
|
||||||
if err = im.cacheClient.Set(tokenKey, issuedToken, im.authenticateOptions.TokenExpiration); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
im.logLogin(user.Username, ip, time.Now())
|
|
||||||
|
|
||||||
return &oauth2.Token{AccessToken: issuedToken}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) logLogin(username, ip string, loginTime time.Time) {
|
|
||||||
if ip != "" {
|
|
||||||
_ = im.cacheClient.Set(loginKeyForUsername(username, loginTime.UTC().Format("2006-01-02T15:04:05Z"), ip), "", 30*24*time.Hour)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) LoginHistory(username string) ([]string, error) {
|
|
||||||
keys, err := im.cacheClient.Keys(loginKeyForUsername(username, "*", "*"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) DescribeUser(username string) (*iam.User, error) {
|
|
||||||
return im.ldapClient.Get(username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) getLastLoginTime(username string) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) DeleteUser(username string) error {
|
|
||||||
return im.ldapClient.Delete(username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) CreateUser(user *iam.User) (*iam.User, error) {
|
|
||||||
err := im.ldapClient.Create(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) VerifyToken(tokenString string) (*iam.User, error) {
|
|
||||||
providedUser, err := im.issuer.Verify(tokenString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := im.ldapClient.Get(providedUser.Name())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) uidNumberNext() int {
|
|
||||||
// TODO fix me
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
func (im *imOperator) GetUserRoles(username string) ([]*rbacv1.Role, error) {
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im *imOperator) GetUserRole(namespace string, username string) (*rbacv1.Role, error) {
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
func authenticationFailedKeyForUsername(username, failedTimestamp string) string {
|
|
||||||
return fmt.Sprintf("kubesphere:authfailed:%s:%s", username, failedTimestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tokenKeyForUsername(username, token string) string {
|
|
||||||
return fmt.Sprintf("kubesphere:users:%s:token:%s", username, token)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loginKeyForUsername(username, loginTimestamp, ip string) string {
|
|
||||||
return fmt.Sprintf("kubesphere:users:%s:login-log:%s:%s", username, loginTimestamp, ip)
|
|
||||||
}
|
|
||||||
25
pkg/models/iam/im/fake_operator.go
Normal file
25
pkg/models/iam/im/fake_operator.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 "kubesphere.io/kubesphere/pkg/simple/client/ldap"
|
||||||
|
|
||||||
|
func NewFakeOperator() IdentityManagementInterface {
|
||||||
|
return NewLDAPOperator(ldap.NewSimpleLdap())
|
||||||
|
}
|
||||||
94
pkg/models/iam/im/im.go
Normal file
94
pkg/models/iam/im/im.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||||
|
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IdentityManagementInterface interface {
|
||||||
|
CreateUser(user *iam.User) (*iam.User, error)
|
||||||
|
DeleteUser(username string) error
|
||||||
|
ModifyUser(user *iam.User) (*iam.User, error)
|
||||||
|
DescribeUser(username string) (*iam.User, error)
|
||||||
|
Authenticate(username, password string) (*iam.User, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type imOperator struct {
|
||||||
|
ldapClient ldap.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
AuthRateLimitExceeded = errors.New("user auth rate limit exceeded")
|
||||||
|
UserAlreadyExists = errors.New("user already exists")
|
||||||
|
UserNotExists = errors.New("user not exists")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLDAPOperator(ldapClient ldap.Interface) IdentityManagementInterface {
|
||||||
|
return &imOperator{
|
||||||
|
ldapClient: ldapClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *imOperator) ModifyUser(user *iam.User) (*iam.User, error) {
|
||||||
|
|
||||||
|
err := im.ldapClient.Update(user)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return im.ldapClient.Get(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *imOperator) Authenticate(username, password string) (*iam.User, error) {
|
||||||
|
|
||||||
|
user, err := im.ldapClient.Get(username)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = im.ldapClient.Authenticate(user.Name, password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *imOperator) DescribeUser(username string) (*iam.User, error) {
|
||||||
|
return im.ldapClient.Get(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *imOperator) DeleteUser(username string) error {
|
||||||
|
return im.ldapClient.Delete(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *imOperator) CreateUser(user *iam.User) (*iam.User, error) {
|
||||||
|
err := im.ldapClient.Create(user)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
@@ -16,4 +16,4 @@
|
|||||||
* /
|
* /
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package iam
|
package im
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,213 +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 iam
|
|
||||||
|
|
||||||
import (
|
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
|
||||||
"kubesphere.io/kubesphere/pkg/models/iam/policy"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RulesMatchesRequired(rules []rbacv1.PolicyRule, required rbacv1.PolicyRule) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
if ruleMatchesRequired(rule, required) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func rulesMatchesAction(rules []rbacv1.PolicyRule, action policy.Action) bool {
|
|
||||||
|
|
||||||
for _, required := range action.Rules {
|
|
||||||
if !RulesMatchesRequired(rules, required) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func ruleMatchesRequired(rule rbacv1.PolicyRule, required rbacv1.PolicyRule) bool {
|
|
||||||
|
|
||||||
if len(required.NonResourceURLs) == 0 {
|
|
||||||
for _, apiGroup := range required.APIGroups {
|
|
||||||
for _, resource := range required.Resources {
|
|
||||||
resources := strings.Split(resource, "/")
|
|
||||||
resource = resources[0]
|
|
||||||
var subsource string
|
|
||||||
if len(resources) > 1 {
|
|
||||||
subsource = resources[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(required.ResourceNames) == 0 {
|
|
||||||
for _, verb := range required.Verbs {
|
|
||||||
if !ruleMatchesRequest(rule, apiGroup, "", resource, subsource, "", verb) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, resourceName := range required.ResourceNames {
|
|
||||||
for _, verb := range required.Verbs {
|
|
||||||
if !ruleMatchesRequest(rule, apiGroup, "", resource, subsource, resourceName, verb) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, apiGroup := range required.APIGroups {
|
|
||||||
for _, nonResourceURL := range required.NonResourceURLs {
|
|
||||||
for _, verb := range required.Verbs {
|
|
||||||
if !ruleMatchesRequest(rule, apiGroup, nonResourceURL, "", "", "", verb) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func ruleMatchesResources(rule rbacv1.PolicyRule, apiGroup string, resource string, subresource string, resourceName string) bool {
|
|
||||||
|
|
||||||
if resource == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasString(rule.APIGroups, apiGroup) && !hasString(rule.APIGroups, rbacv1.ResourceAll) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(rule.ResourceNames) > 0 && !hasString(rule.ResourceNames, resourceName) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
combinedResource := resource
|
|
||||||
|
|
||||||
if subresource != "" {
|
|
||||||
combinedResource = combinedResource + "/" + subresource
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, res := range rule.Resources {
|
|
||||||
|
|
||||||
// match "*"
|
|
||||||
if res == rbacv1.ResourceAll || res == combinedResource {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// match "*/subresource"
|
|
||||||
if len(subresource) > 0 && strings.HasPrefix(res, "*/") && subresource == strings.TrimLeft(res, "*/") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// match "resource/*"
|
|
||||||
if strings.HasSuffix(res, "/*") && resource == strings.TrimRight(res, "/*") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func ruleMatchesRequest(rule rbacv1.PolicyRule, apiGroup string, nonResourceURL string, resource string, subresource string, resourceName string, verb string) bool {
|
|
||||||
|
|
||||||
if !hasString(rule.Verbs, verb) && !hasString(rule.Verbs, rbacv1.VerbAll) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if nonResourceURL == "" {
|
|
||||||
return ruleMatchesResources(rule, apiGroup, resource, subresource, resourceName)
|
|
||||||
} else {
|
|
||||||
return ruleMatchesNonResource(rule, nonResourceURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ruleMatchesNonResource(rule rbacv1.PolicyRule, nonResourceURL string) bool {
|
|
||||||
|
|
||||||
if nonResourceURL == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, spec := range rule.NonResourceURLs {
|
|
||||||
if pathMatches(nonResourceURL, spec) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathMatches(path, spec string) bool {
|
|
||||||
// Allow wildcard match
|
|
||||||
if spec == "*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Allow exact match
|
|
||||||
if spec == path {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Allow a trailing * subpath match
|
|
||||||
if strings.HasSuffix(spec, "*") && strings.HasPrefix(path, strings.TrimRight(spec, "*")) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasString(slice []string, value string) bool {
|
|
||||||
for _, s := range slice {
|
|
||||||
if s == value {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func ContainsUser(subjects interface{}, username string) bool {
|
|
||||||
switch subjects.(type) {
|
|
||||||
case []*rbacv1.Subject:
|
|
||||||
for _, subject := range subjects.([]*rbacv1.Subject) {
|
|
||||||
if subject.Kind == rbacv1.UserKind && subject.Name == username {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []rbacv1.Subject:
|
|
||||||
for _, subject := range subjects.([]rbacv1.Subject) {
|
|
||||||
if subject.Kind == rbacv1.UserKind && subject.Name == username {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []iam.User:
|
|
||||||
for _, u := range subjects.([]iam.User) {
|
|
||||||
if u.Username == username {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case []*iam.User:
|
|
||||||
for _, u := range subjects.([]*iam.User) {
|
|
||||||
if u.Username == username {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user