istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/writer/compare/sds/util.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 sdscompare 16 17 import ( 18 "crypto/x509" 19 "encoding/pem" 20 "fmt" 21 "time" 22 23 envoy_admin "github.com/envoyproxy/go-control-plane/envoy/admin/v3" 24 auth "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" 25 26 "istio.io/istio/istioctl/pkg/util/configdump" 27 "istio.io/istio/pkg/log" 28 ) 29 30 // SecretItemDiff represents a secret that has been diffed between nodeagent and proxy 31 type SecretItemDiff struct { 32 Agent string `json:"agent"` 33 Proxy string `json:"proxy"` 34 SecretItem 35 } 36 37 // SecretItem is an intermediate representation of secrets, used to provide a common 38 // format between the envoy proxy secrets and node agent output which can be diffed 39 type SecretItem struct { 40 Name string `json:"resource_name"` 41 Data string `json:"cert"` 42 Source string `json:"source"` 43 Destination string `json:"destination"` 44 State string `json:"state"` 45 SecretMeta 46 } 47 48 // SecretMeta holds selected fields which can be extracted from parsed x509 cert 49 type SecretMeta struct { 50 Valid bool `json:"cert_valid"` 51 SerialNumber string `json:"serial_number"` 52 NotAfter string `json:"not_after"` 53 NotBefore string `json:"not_before"` 54 Type string `json:"type"` 55 } 56 57 // NewSecretItemBuilder returns a new builder to create a secret item 58 func NewSecretItemBuilder() SecretItemBuilder { 59 return &secretItemBuilder{} 60 } 61 62 // SecretItemBuilder wraps the process of setting fields for the SecretItem 63 // and builds the Metadata fields from the cert contents behind the scenes 64 type SecretItemBuilder interface { 65 Name(string) SecretItemBuilder 66 Data(string) SecretItemBuilder 67 Source(string) SecretItemBuilder 68 Destination(string) SecretItemBuilder 69 State(string) SecretItemBuilder 70 Build() (SecretItem, error) 71 } 72 73 // secretItemBuilder implements SecretItemBuilder, and acts as an intermediate before SecretItem generation 74 type secretItemBuilder struct { 75 name string 76 data string 77 source string 78 dest string 79 state string 80 SecretMeta 81 } 82 83 // Name sets the name field on a secretItemBuilder 84 func (s *secretItemBuilder) Name(name string) SecretItemBuilder { 85 s.name = name 86 return s 87 } 88 89 // Data sets the data field on a secretItemBuilder 90 func (s *secretItemBuilder) Data(data string) SecretItemBuilder { 91 s.data = data 92 return s 93 } 94 95 // Source sets the source field on a secretItemBuilder 96 func (s *secretItemBuilder) Source(source string) SecretItemBuilder { 97 s.source = source 98 return s 99 } 100 101 // Destination sets the destination field on a secretItemBuilder 102 func (s *secretItemBuilder) Destination(dest string) SecretItemBuilder { 103 s.dest = dest 104 return s 105 } 106 107 // State sets the state of the secret on the agent or sidecar 108 func (s *secretItemBuilder) State(state string) SecretItemBuilder { 109 s.state = state 110 return s 111 } 112 113 // Build takes the set fields from the builder and constructs the actual SecretItem 114 // including generating the SecretMeta from the supplied cert data, if present 115 func (s *secretItemBuilder) Build() (SecretItem, error) { 116 result := SecretItem{ 117 Name: s.name, 118 Data: s.data, 119 Source: s.source, 120 Destination: s.dest, 121 State: s.state, 122 } 123 124 var meta SecretMeta 125 var err error 126 if s.data != "" { 127 meta, err = secretMetaFromCert([]byte(s.data)) 128 if err != nil { 129 log.Debugf("failed to parse secret resource %s from source %s: %v", 130 s.name, s.source, err) 131 result.Valid = false 132 return result, nil 133 } 134 result.SecretMeta = meta 135 result.Valid = meta.Valid 136 return result, nil 137 } 138 result.Valid = false 139 return result, nil 140 } 141 142 // GetEnvoySecrets parses the secrets section of the config dump into []SecretItem 143 func GetEnvoySecrets( 144 wrapper *configdump.Wrapper, 145 ) ([]SecretItem, error) { 146 secretConfigDump, err := wrapper.GetSecretConfigDump() 147 if err != nil { 148 return nil, err 149 } 150 151 proxySecretItems := make([]SecretItem, 0) 152 for _, warmingSecret := range secretConfigDump.DynamicWarmingSecrets { 153 secret, err := parseDynamicSecret(warmingSecret, "WARMING") 154 if err != nil { 155 return nil, fmt.Errorf("failed building warming secret %s: %v", 156 warmingSecret.Name, err) 157 } 158 proxySecretItems = append(proxySecretItems, secret) 159 } 160 for _, activeSecret := range secretConfigDump.DynamicActiveSecrets { 161 secret, err := parseDynamicSecret(activeSecret, "ACTIVE") 162 if err != nil { 163 return nil, fmt.Errorf("failed building warming secret %s: %v", 164 activeSecret.Name, err) 165 } 166 if activeSecret.VersionInfo == "uninitialized" { 167 secret.State = "UNINITIALIZED" 168 } 169 proxySecretItems = append(proxySecretItems, secret) 170 } 171 return proxySecretItems, nil 172 } 173 174 func parseDynamicSecret(s *envoy_admin.SecretsConfigDump_DynamicSecret, state string) (SecretItem, error) { 175 builder := NewSecretItemBuilder() 176 builder.Name(s.Name).State(state) 177 178 secretTyped := &auth.Secret{} 179 err := s.GetSecret().UnmarshalTo(secretTyped) 180 if err != nil { 181 return SecretItem{}, err 182 } 183 184 certChainSecret := secretTyped. 185 GetTlsCertificate(). 186 GetCertificateChain(). 187 GetInlineBytes() 188 caDataSecret := secretTyped. 189 GetValidationContext(). 190 GetTrustedCa(). 191 GetInlineBytes() 192 193 // seems as though the most straightforward way to tell whether this is a root ca or not 194 // is to check whether the inline bytes of the cert chain or the trusted ca field is zero length 195 if len(certChainSecret) > 0 { 196 builder.Data(string(certChainSecret)) 197 } else if len(caDataSecret) > 0 { 198 builder.Data(string(caDataSecret)) 199 } 200 201 secret, err := builder.Build() 202 if err != nil { 203 return SecretItem{}, fmt.Errorf("error building secret: %v", err) 204 } 205 206 return secret, nil 207 } 208 209 func secretMetaFromCert(rawCert []byte) (SecretMeta, error) { 210 block, _ := pem.Decode(rawCert) 211 if block == nil { 212 return SecretMeta{}, fmt.Errorf("failed to parse certificate PEM") 213 } 214 cert, err := x509.ParseCertificate(block.Bytes) 215 if err != nil { 216 return SecretMeta{}, err 217 } 218 var certType string 219 if cert.IsCA { 220 certType = "CA" 221 } else { 222 certType = "Cert Chain" 223 } 224 225 today := time.Now() 226 return SecretMeta{ 227 SerialNumber: fmt.Sprintf("%x", cert.SerialNumber), 228 NotAfter: cert.NotAfter.Format(time.RFC3339), 229 NotBefore: cert.NotBefore.Format(time.RFC3339), 230 Type: certType, 231 Valid: today.After(cert.NotBefore) && today.Before(cert.NotAfter), 232 }, nil 233 }