istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/security/trustdomain/bundle.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package trustdomain
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	"istio.io/istio/pilot/pkg/features"
    22  	"istio.io/istio/pkg/config/constants"
    23  	istiolog "istio.io/istio/pkg/log"
    24  )
    25  
    26  var authzLog = istiolog.RegisterScope("authorization", "Istio Authorization Policy")
    27  
    28  type Bundle struct {
    29  	// Contain the local trust domain and its aliases.
    30  	// The trust domain corresponds to the trust root of a system.
    31  	// Refer to [SPIFFE-ID](https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain)
    32  	// The trust domain aliases represent the aliases of `trust_domain`.
    33  	// For example, if we have
    34  	// trustDomain: td1, trustDomainAliases: ["td2", "td3"]
    35  	// Any service with the identity `td1/ns/foo/sa/a-service-account`, `td2/ns/foo/sa/a-service-account`,
    36  	// or `td3/ns/foo/sa/a-service-account` will be treated the same in the Istio mesh.
    37  	TrustDomains []string
    38  }
    39  
    40  // NewBundle returns a new trust domain bundle.
    41  func NewBundle(trustDomain string, trustDomainAliases []string) Bundle {
    42  	return Bundle{
    43  		// Put the new trust domain to the beginning of the list to avoid changing existing tests.
    44  		TrustDomains: append([]string{trustDomain}, trustDomainAliases...),
    45  	}
    46  }
    47  
    48  // ReplaceTrustDomainAliases checks the existing principals and returns a list of new principals
    49  // with the current trust domain and its aliases.
    50  // For example, for a user "bar" in namespace "foo".
    51  // If the local trust domain is "td2" and its alias is "td1" (migrating from td1 to td2),
    52  // replaceTrustDomainAliases returns ["td2/ns/foo/sa/bar", "td1/ns/foo/sa/bar]].
    53  func (t Bundle) ReplaceTrustDomainAliases(principals []string) []string {
    54  	principalsIncludingAliases := []string{}
    55  	for _, principal := range principals {
    56  		isTrustDomainBeingEnforced := isTrustDomainBeingEnforced(principal)
    57  		// Return the existing principals if the policy doesn't care about the trust domain.
    58  		if !isTrustDomainBeingEnforced {
    59  			principalsIncludingAliases = append(principalsIncludingAliases, principal)
    60  			continue
    61  		}
    62  		trustDomainFromPrincipal, err := getTrustDomainFromSpiffeIdentity(principal)
    63  		if err != nil {
    64  			authzLog.Errorf("unexpected incorrect Spiffe format: %s", principal)
    65  			principalsIncludingAliases = append(principalsIncludingAliases, principal)
    66  			continue
    67  		}
    68  		// Only generate configuration if the extracted trust domain from the policy is part of the trust domain list,
    69  		// or if the extracted/existing trust domain is "cluster.local", which is a pointer to the local trust domain
    70  		// and its aliases.
    71  		if stringMatch(trustDomainFromPrincipal, t.TrustDomains) || trustDomainFromPrincipal == constants.DefaultClusterLocalDomain {
    72  			// Generate configuration for trust domain and trust domain aliases.
    73  			principalsIncludingAliases = append(principalsIncludingAliases, t.replaceTrustDomains(principal, trustDomainFromPrincipal)...)
    74  		} else {
    75  			msg := fmt.Sprintf("Trust domain %s from principal %s does not match the current trust "+
    76  				"domain or its aliases", trustDomainFromPrincipal, principal)
    77  			// when SkipValidateTrustDomain is being used the message isn't very meaningful so we'll log it at a lower level
    78  			// logging it at this level may help users who are looking to disable skipping validation understand if it's safe
    79  			if !features.SkipValidateTrustDomain {
    80  				authzLog.Warn(msg)
    81  			} else {
    82  				authzLog.Debug(msg)
    83  			}
    84  			// If the trust domain from the existing doesn't match with the new trust domain aliases or "cluster.local",
    85  			// keep the policy as it is.
    86  			principalsIncludingAliases = append(principalsIncludingAliases, principal)
    87  		}
    88  	}
    89  	return principalsIncludingAliases
    90  }
    91  
    92  // replaceTrustDomains replace the given principal's trust domain with the trust domains from the
    93  // trustDomains list and return the new principals.
    94  func (t Bundle) replaceTrustDomains(principal, trustDomainFromPrincipal string) []string {
    95  	principalsForAliases := []string{}
    96  	for _, td := range t.TrustDomains {
    97  		// If the trust domain has a prefix * (e.g. *local from *local/ns/foo/sa/bar), keep the principal
    98  		// as-is for the matched trust domain. For others, replace the trust domain with the new trust domain
    99  		// or alias.
   100  		var newPrincipal string
   101  		var err error
   102  		if suffixMatch(td, trustDomainFromPrincipal) {
   103  			newPrincipal = principal
   104  		} else {
   105  			newPrincipal, err = replaceTrustDomainInPrincipal(td, principal)
   106  			if err != nil {
   107  				authzLog.Errorf("Failed to replace trust domain with %s from principal %s: %v", td, principal, err)
   108  				continue
   109  			}
   110  		}
   111  		// Check to make sure we don't generate duplicated principals. This happens when trust domain
   112  		// has a * prefix. For example, "*-td" can match with "old-td" and "new-td", but we only want
   113  		// to keep the principal as-is in the generated config, .i.e. *-td.
   114  		if !isKeyInList(newPrincipal, principalsForAliases) {
   115  			principalsForAliases = append(principalsForAliases, newPrincipal)
   116  		}
   117  	}
   118  	return principalsForAliases
   119  }
   120  
   121  // replaceTrustDomainInPrincipal returns a new SPIFFE identity with the new trust domain.
   122  // The trust domain corresponds to the trust root of a system.
   123  // Refer to
   124  // [SPIFFE-ID](https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain)
   125  // In Istio authorization, an identity is presented in the format:
   126  // <trust-domain>/ns/<some-namespace>/sa/<some-service-account>
   127  func replaceTrustDomainInPrincipal(trustDomain string, principal string) (string, error) {
   128  	identityParts := strings.Split(principal, "/")
   129  	// A valid SPIFFE identity in authorization has no SPIFFE:// prefix.
   130  	// It is presented as <trust-domain>/ns/<some-namespace>/sa/<some-service-account>
   131  	if len(identityParts) != 5 {
   132  		return "", fmt.Errorf("wrong SPIFFE format: %s", principal)
   133  	}
   134  	return fmt.Sprintf("%s/%s", trustDomain, strings.Join(identityParts[1:], "/")), nil
   135  }
   136  
   137  // isTrustDomainBeingEnforced checks whether the trust domain is being checked in the filter or not.
   138  // For example, in the principal "*/ns/foo/sa/bar", the trust domain is * and it matches to any trust domain,
   139  // so it won't be checked in the filter.
   140  func isTrustDomainBeingEnforced(principal string) bool {
   141  	identityParts := strings.Split(principal, "/")
   142  	if len(identityParts) != 5 {
   143  		// If a principal is mis-configured and doesn't follow Spiffe format, e.g. "sa/bar",
   144  		// there is really no trust domain from the principal, so the trust domain is also considered not being enforced.
   145  		return false
   146  	}
   147  	// Check if the first part of the spiffe string is "*" (as opposed to *-something or "").
   148  	return identityParts[0] != "*"
   149  }
   150  
   151  // getTrustDomainFromSpiffeIdentity gets the trust domain from the given principal and expects
   152  // principal to have the right SPIFFE format.
   153  func getTrustDomainFromSpiffeIdentity(principal string) (string, error) {
   154  	identityParts := strings.Split(principal, "/")
   155  	// A valid SPIFFE identity in authorization has no SPIFFE:// prefix.
   156  	// It is presented as <trust-domain>/ns/<some-namespace>/sa/<some-service-account>
   157  	if len(identityParts) != 5 {
   158  		return "", fmt.Errorf("wrong SPIFFE format: %s", principal)
   159  	}
   160  	trustDomain := identityParts[0]
   161  	return trustDomain, nil
   162  }