2247d8c3a2
* + 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>
150 lines
4.3 KiB
Go
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)
|
|
}
|