Files
portainer/api/kubernetes/cli/client.go
T
Yi Chen 2247d8c3a2 (feat)k8s/RBAC: Provide Portainer RBAC functionality for Kubernetes endpoints (#35)
* + endpoint and namespace level authorizations
+ user namespace authorization API
+ k8s client setup service account with k8s roles and policies by portainer role
* User authorization changes refresh token cache
* rbac authorizes k8s requests
* CE to EE migrator to include new authorizations

* code clean up
* comments

* * merge in the RestrictDefaultNamespace changes

* - remove unnecessary check for default namespace

* + updates namespace access policies when generating token

* * updates namespace access policies when querying the user namespace endpoint

* + k8s rule in rbac.go for endpoint access test
+ missing k8s cluster rules for different roles

* feat(rbac): update kube rbac

* feat(rbac): use the authorization directive

* feat(rbac): Update namespace access policies when user/team is deleted

* refactor(app): use new angular-multi-select capabilities

* feat(rbac): fix authorizations

* feat(rbac): fix userAccessPolicies update bug

* feat(rbac): add W applications authorizations

* feat(rbac): add application details W authorizations

* feat(rbac): add configurations W autohorizations

* feat(rbac): add configuration details W authorizations

* feat(rbac): add volumes W authorizations

* feat(rbac): add volume details W authorizations

* feat(rbac): add componentstatus to portainer-view role and add cluster/node authorizations

* fix(rbac): disable application note for non authorized user

* fix(rbac): add endpoints list and components status to portainer-basic

* fix(rbac): allow user to access default namespace when restrict default namespace isn't activated

* fix(rbac): remove default namespace from useraccesspolicies when restrict default namespace isn't activated

* fix(rbac): change some things

* fix(rbac): allow standard user to access container console

* - removed unused parameter

* fix(rbac): fix team authorizations

Co-authored-by: Maxime Bajeux <max.bajeux@gmail.com>
Co-authored-by: xAt0mZ <baron_l@epitech.eu>
2020-11-03 22:08:09 +13:00

150 lines
4.3 KiB
Go

package cli
import (
"errors"
"fmt"
"net/http"
"strconv"
cmap "github.com/orcaman/concurrent-map"
portainer "github.com/portainer/portainer/api"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
type (
// ClientFactory is used to create Kubernetes clients
ClientFactory struct {
reverseTunnelService portainer.ReverseTunnelService
signatureService portainer.DigitalSignatureService
instanceID string
endpointClients cmap.ConcurrentMap
}
// KubeClient represent a service used to execute Kubernetes operations
KubeClient struct {
cli *kubernetes.Clientset
instanceID string
}
)
// NewClientFactory returns a new instance of a ClientFactory
func NewClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, instanceID string) *ClientFactory {
return &ClientFactory{
signatureService: signatureService,
reverseTunnelService: reverseTunnelService,
instanceID: instanceID,
endpointClients: cmap.New(),
}
}
// GetKubeClient checks if an existing client is already registered for the endpoint and returns it if one is found.
// If no client is registered, it will create a new client, register it, and returns it.
func (factory *ClientFactory) GetKubeClient(endpoint *portainer.Endpoint) (portainer.KubeClient, error) {
key := strconv.Itoa(int(endpoint.ID))
client, ok := factory.endpointClients.Get(key)
if !ok {
client, err := factory.createKubeClient(endpoint)
if err != nil {
return nil, err
}
factory.endpointClients.Set(key, client)
return client, nil
}
return client.(portainer.KubeClient), nil
}
func (factory *ClientFactory) createKubeClient(endpoint *portainer.Endpoint) (portainer.KubeClient, error) {
cli, err := factory.CreateClient(endpoint)
if err != nil {
return nil, err
}
kubecli := &KubeClient{
cli: cli,
instanceID: factory.instanceID,
}
return kubecli, nil
}
// CreateClient returns a pointer to a new Clientset instance
func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint) (*kubernetes.Clientset, error) {
switch endpoint.Type {
case portainer.KubernetesLocalEnvironment:
return buildLocalClient()
case portainer.AgentOnKubernetesEnvironment:
return factory.buildAgentClient(endpoint)
case portainer.EdgeAgentOnKubernetesEnvironment:
return factory.buildEdgeClient(endpoint)
}
return nil, errors.New("unsupported endpoint type")
}
type agentHeaderRoundTripper struct {
signatureHeader string
publicKeyHeader string
roundTripper http.RoundTripper
}
// RoundTrip is the implementation of the http.RoundTripper interface.
// It decorates the request with specific agent headers
func (rt *agentHeaderRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Add(portainer.PortainerAgentPublicKeyHeader, rt.publicKeyHeader)
req.Header.Add(portainer.PortainerAgentSignatureHeader, rt.signatureHeader)
return rt.roundTripper.RoundTrip(req)
}
func (factory *ClientFactory) buildAgentClient(endpoint *portainer.Endpoint) (*kubernetes.Clientset, error) {
endpointURL := fmt.Sprintf("https://%s/kubernetes", endpoint.URL)
signature, err := factory.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
if err != nil {
return nil, err
}
config, err := clientcmd.BuildConfigFromFlags(endpointURL, "")
if err != nil {
return nil, err
}
config.Insecure = true
config.Wrap(func(rt http.RoundTripper) http.RoundTripper {
return &agentHeaderRoundTripper{
signatureHeader: signature,
publicKeyHeader: factory.signatureService.EncodedPublicKey(),
roundTripper: rt,
}
})
return kubernetes.NewForConfig(config)
}
func (factory *ClientFactory) buildEdgeClient(endpoint *portainer.Endpoint) (*kubernetes.Clientset, error) {
tunnel := factory.reverseTunnelService.GetTunnelDetails(endpoint.ID)
endpointURL := fmt.Sprintf("http://localhost:%d/kubernetes", tunnel.Port)
config, err := clientcmd.BuildConfigFromFlags(endpointURL, "")
if err != nil {
return nil, err
}
config.Insecure = true
return kubernetes.NewForConfig(config)
}
func buildLocalClient() (*kubernetes.Clientset, error) {
config, err := rest.InClusterConfig()
if err != nil {
return nil, err
}
return kubernetes.NewForConfig(config)
}