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 }