blob: 9b18a42cb9743f21f10a4eb5188946e129c7acd1 [file] [log] [blame]
// Copyright Istio 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 model
import (
"crypto/md5"
"fmt"
"strings"
"time"
)
import (
"istio.io/api/security/v1beta1"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/labels"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collections"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/gvk"
)
// MutualTLSMode is the mutual TLS mode specified by authentication policy.
type MutualTLSMode int
const (
// MTLSUnknown is used to indicate the variable hasn't been initialized correctly (with the authentication policy).
MTLSUnknown MutualTLSMode = iota
// MTLSDisable if authentication policy disable mTLS.
MTLSDisable
// MTLSPermissive if authentication policy enable mTLS in permissive mode.
MTLSPermissive
// MTLSStrict if authentication policy enable mTLS in strict mode.
MTLSStrict
)
// String converts MutualTLSMode to human readable string for debugging.
func (mode MutualTLSMode) String() string {
switch mode {
case MTLSDisable:
return "DISABLE"
case MTLSPermissive:
return "PERMISSIVE"
case MTLSStrict:
return "STRICT"
default:
return "UNKNOWN"
}
}
// ConvertToMutualTLSMode converts from peer authn MTLS mode (`PeerAuthentication_MutualTLS_Mode`)
// to the MTLS mode specified by authn policy.
func ConvertToMutualTLSMode(mode v1beta1.PeerAuthentication_MutualTLS_Mode) MutualTLSMode {
switch mode {
case v1beta1.PeerAuthentication_MutualTLS_DISABLE:
return MTLSDisable
case v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE:
return MTLSPermissive
case v1beta1.PeerAuthentication_MutualTLS_STRICT:
return MTLSStrict
default:
return MTLSUnknown
}
}
// AuthenticationPolicies organizes authentication (mTLS + JWT) policies by namespace.
type AuthenticationPolicies struct {
// Maps from namespace to the v1beta1 authentication policies.
requestAuthentications map[string][]config.Config
peerAuthentications map[string][]config.Config
// namespaceMutualTLSMode is the MutualTLSMode corresponding to the namespace-level PeerAuthentication.
// All namespace-level policies, and only them, are added to this map. If the policy mTLS mode is set
// to UNSET, it will be resolved to the value set by mesh policy if exist (i.e not UNKNOWN), or MTLSPermissive
// otherwise.
namespaceMutualTLSMode map[string]MutualTLSMode
// globalMutualTLSMode is the MutualTLSMode corresponding to the mesh-level PeerAuthentication.
// This value can be MTLSUnknown, if there is no mesh-level policy.
globalMutualTLSMode MutualTLSMode
rootNamespace string
// aggregateVersion contains the versions of all peer authentications.
aggregateVersion string
}
// initAuthenticationPolicies creates a new AuthenticationPolicies struct and populates with the
// authentication policies in the mesh environment.
func initAuthenticationPolicies(env *Environment) (*AuthenticationPolicies, error) {
policy := &AuthenticationPolicies{
requestAuthentications: map[string][]config.Config{},
peerAuthentications: map[string][]config.Config{},
globalMutualTLSMode: MTLSUnknown,
rootNamespace: env.Mesh().GetRootNamespace(),
}
if configs, err := env.List(
gvk.RequestAuthentication, NamespaceAll); err == nil {
sortConfigByCreationTime(configs)
policy.addRequestAuthentication(configs)
} else {
return nil, err
}
if configs, err := env.List(
gvk.PeerAuthentication, NamespaceAll); err == nil {
policy.addPeerAuthentication(configs)
} else {
return nil, err
}
return policy, nil
}
func (policy *AuthenticationPolicies) addRequestAuthentication(configs []config.Config) {
for _, config := range configs {
policy.requestAuthentications[config.Namespace] = append(policy.requestAuthentications[config.Namespace], config)
}
}
func (policy *AuthenticationPolicies) addPeerAuthentication(configs []config.Config) {
// Sort configs in ascending order by their creation time.
sortConfigByCreationTime(configs)
foundNamespaceMTLS := make(map[string]v1beta1.PeerAuthentication_MutualTLS_Mode)
// Track which namespace/mesh level policy seen so far to make sure the oldest one is used.
seenNamespaceOrMeshConfig := make(map[string]time.Time)
versions := []string{}
for _, config := range configs {
versions = append(versions, config.UID+"."+config.ResourceVersion)
// Mesh & namespace level policy are those that have empty selector.
spec := config.Spec.(*v1beta1.PeerAuthentication)
if spec.Selector == nil || len(spec.Selector.MatchLabels) == 0 {
if t, ok := seenNamespaceOrMeshConfig[config.Namespace]; ok {
log.Warnf(
"Namespace/mesh-level PeerAuthentication is already defined for %q at time %v. Ignore %q which was created at time %v",
config.Namespace, t, config.Name, config.CreationTimestamp)
continue
}
seenNamespaceOrMeshConfig[config.Namespace] = config.CreationTimestamp
mode := v1beta1.PeerAuthentication_MutualTLS_UNSET
if spec.Mtls != nil {
mode = spec.Mtls.Mode
}
if config.Namespace == policy.rootNamespace {
// This is mesh-level policy. UNSET is treated as permissive for mesh-policy.
if mode == v1beta1.PeerAuthentication_MutualTLS_UNSET {
policy.globalMutualTLSMode = MTLSPermissive
} else {
policy.globalMutualTLSMode = ConvertToMutualTLSMode(mode)
}
} else {
// For regular namespace, just add to the intermediate map.
foundNamespaceMTLS[config.Namespace] = mode
}
}
// Add the config to the map by namespace for future look up. This is done after namespace/mesh
// singleton check so there should be at most one namespace/mesh config is added to the map.
policy.peerAuthentications[config.Namespace] = append(policy.peerAuthentications[config.Namespace], config)
}
policy.aggregateVersion = fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(versions, ";"))))
// Process found namespace-level policy.
policy.namespaceMutualTLSMode = make(map[string]MutualTLSMode, len(foundNamespaceMTLS))
inheritedMTLSMode := policy.globalMutualTLSMode
if inheritedMTLSMode == MTLSUnknown {
// If the mesh policy is not explicitly presented, use default value MTLSPermissive.
inheritedMTLSMode = MTLSPermissive
}
for ns, mtlsMode := range foundNamespaceMTLS {
if mtlsMode == v1beta1.PeerAuthentication_MutualTLS_UNSET {
policy.namespaceMutualTLSMode[ns] = inheritedMTLSMode
} else {
policy.namespaceMutualTLSMode[ns] = ConvertToMutualTLSMode(mtlsMode)
}
}
}
// GetNamespaceMutualTLSMode returns the MutualTLSMode as defined by a namespace or mesh level
// PeerAuthentication. The return value could be `MTLSUnknown` if there is no mesh nor namespace
// PeerAuthentication policy for the given namespace.
func (policy *AuthenticationPolicies) GetNamespaceMutualTLSMode(namespace string) MutualTLSMode {
if mode, ok := policy.namespaceMutualTLSMode[namespace]; ok {
return mode
}
return policy.globalMutualTLSMode
}
// GetJwtPoliciesForWorkload returns a list of JWT policies matching to labels.
func (policy *AuthenticationPolicies) GetJwtPoliciesForWorkload(namespace string,
workloadLabels labels.Instance) []*config.Config {
return getConfigsForWorkload(policy.requestAuthentications, policy.rootNamespace, namespace, workloadLabels)
}
// GetPeerAuthenticationsForWorkload returns a list of peer authentication policies matching to labels.
func (policy *AuthenticationPolicies) GetPeerAuthenticationsForWorkload(namespace string,
workloadLabels labels.Instance) []*config.Config {
return getConfigsForWorkload(policy.peerAuthentications, policy.rootNamespace, namespace, workloadLabels)
}
// GetRootNamespace return root namespace that is tracked by the policy object.
func (policy *AuthenticationPolicies) GetRootNamespace() string {
return policy.rootNamespace
}
// GetVersion return versions of all peer authentications..
func (policy *AuthenticationPolicies) GetVersion() string {
return policy.aggregateVersion
}
func getConfigsForWorkload(configsByNamespace map[string][]config.Config,
rootNamespace string,
namespace string,
workloadLabels labels.Instance) []*config.Config {
configs := make([]*config.Config, 0)
lookupInNamespaces := []string{namespace}
if namespace != rootNamespace {
// Only check the root namespace if the (workload) namespace is not already the root namespace
// to avoid double inclusion.
lookupInNamespaces = append(lookupInNamespaces, rootNamespace)
}
for _, ns := range lookupInNamespaces {
if nsConfig, ok := configsByNamespace[ns]; ok {
for idx := range nsConfig {
cfg := &nsConfig[idx]
if ns != cfg.Namespace {
// Should never come here. Log warning just in case.
log.Warnf("Seeing config %s with namespace %s in map entry for %s. Ignored", cfg.Name, cfg.Namespace, ns)
continue
}
var selector labels.Instance
switch cfg.GroupVersionKind {
case collections.IstioSecurityV1Beta1Requestauthentications.Resource().GroupVersionKind():
selector = cfg.Spec.(*v1beta1.RequestAuthentication).GetSelector().GetMatchLabels()
case collections.IstioSecurityV1Beta1Peerauthentications.Resource().GroupVersionKind():
selector = cfg.Spec.(*v1beta1.PeerAuthentication).GetSelector().GetMatchLabels()
default:
log.Warnf("Not support authentication type %q", cfg.GroupVersionKind)
continue
}
if selector.SubsetOf(workloadLabels) {
configs = append(configs, cfg)
}
}
}
}
return configs
}