| // 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 sdscompare |
| |
| import ( |
| "crypto/x509" |
| "encoding/pem" |
| "fmt" |
| "time" |
| ) |
| |
| import ( |
| envoy_admin "github.com/envoyproxy/go-control-plane/envoy/admin/v3" |
| auth "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" |
| "istio.io/pkg/log" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/istioctl/pkg/util/configdump" |
| ) |
| |
| // SecretItemDiff represents a secret that has been diffed between nodeagent and proxy |
| type SecretItemDiff struct { |
| Agent string `json:"agent"` |
| Proxy string `json:"proxy"` |
| SecretItem |
| } |
| |
| // SecretItem is an intermediate representation of secrets, used to provide a common |
| // format between the envoy proxy secrets and node agent output which can be diffed |
| type SecretItem struct { |
| Name string `json:"resource_name"` |
| Data string `json:"cert"` |
| Source string `json:"source"` |
| Destination string `json:"destination"` |
| State string `json:"state"` |
| SecretMeta |
| } |
| |
| // SecretMeta holds selected fields which can be extracted from parsed x509 cert |
| type SecretMeta struct { |
| Valid bool `json:"cert_valid"` |
| SerialNumber string `json:"serial_number"` |
| NotAfter string `json:"not_after"` |
| NotBefore string `json:"not_before"` |
| Type string `json:"type"` |
| } |
| |
| // NewSecretItemBuilder returns a new builder to create a secret item |
| func NewSecretItemBuilder() SecretItemBuilder { |
| return &secretItemBuilder{} |
| } |
| |
| // SecretItemBuilder wraps the process of setting fields for the SecretItem |
| // and builds the Metadata fields from the cert contents behind the scenes |
| type SecretItemBuilder interface { |
| Name(string) SecretItemBuilder |
| Data(string) SecretItemBuilder |
| Source(string) SecretItemBuilder |
| Destination(string) SecretItemBuilder |
| State(string) SecretItemBuilder |
| Build() (SecretItem, error) |
| } |
| |
| // secretItemBuilder implements SecretItemBuilder, and acts as an intermediate before SecretItem generation |
| type secretItemBuilder struct { |
| name string |
| data string |
| source string |
| dest string |
| state string |
| SecretMeta |
| } |
| |
| // Name sets the name field on a secretItemBuilder |
| func (s *secretItemBuilder) Name(name string) SecretItemBuilder { |
| s.name = name |
| return s |
| } |
| |
| // Data sets the data field on a secretItemBuilder |
| func (s *secretItemBuilder) Data(data string) SecretItemBuilder { |
| s.data = data |
| return s |
| } |
| |
| // Source sets the source field on a secretItemBuilder |
| func (s *secretItemBuilder) Source(source string) SecretItemBuilder { |
| s.source = source |
| return s |
| } |
| |
| // Destination sets the destination field on a secretItemBuilder |
| func (s *secretItemBuilder) Destination(dest string) SecretItemBuilder { |
| s.dest = dest |
| return s |
| } |
| |
| // State sets the state of the secret on the agent or sidecar |
| func (s *secretItemBuilder) State(state string) SecretItemBuilder { |
| s.state = state |
| return s |
| } |
| |
| // Build takes the set fields from the builder and constructs the actual SecretItem |
| // including generating the SecretMeta from the supplied cert data, if present |
| func (s *secretItemBuilder) Build() (SecretItem, error) { |
| result := SecretItem{ |
| Name: s.name, |
| Data: s.data, |
| Source: s.source, |
| Destination: s.dest, |
| State: s.state, |
| } |
| |
| var meta SecretMeta |
| var err error |
| if s.data != "" { |
| meta, err = secretMetaFromCert([]byte(s.data)) |
| if err != nil { |
| log.Debugf("failed to parse secret resource %s from source %s: %v", |
| s.name, s.source, err) |
| result.Valid = false |
| return result, nil |
| } |
| result.SecretMeta = meta |
| result.Valid = true |
| return result, nil |
| } |
| result.Valid = false |
| return result, nil |
| } |
| |
| // GetEnvoySecrets parses the secrets section of the config dump into []SecretItem |
| func GetEnvoySecrets( |
| wrapper *configdump.Wrapper) ([]SecretItem, error) { |
| secretConfigDump, err := wrapper.GetSecretConfigDump() |
| if err != nil { |
| return nil, err |
| } |
| |
| proxySecretItems := make([]SecretItem, 0) |
| for _, warmingSecret := range secretConfigDump.DynamicWarmingSecrets { |
| secret, err := parseDynamicSecret(warmingSecret, "WARMING") |
| if err != nil { |
| return nil, fmt.Errorf("failed building warming secret %s: %v", |
| warmingSecret.Name, err) |
| } |
| proxySecretItems = append(proxySecretItems, secret) |
| } |
| for _, activeSecret := range secretConfigDump.DynamicActiveSecrets { |
| secret, err := parseDynamicSecret(activeSecret, "ACTIVE") |
| if err != nil { |
| return nil, fmt.Errorf("failed building warming secret %s: %v", |
| activeSecret.Name, err) |
| } |
| if activeSecret.VersionInfo == "uninitialized" { |
| secret.State = "UNINITIALIZED" |
| } |
| proxySecretItems = append(proxySecretItems, secret) |
| } |
| return proxySecretItems, nil |
| } |
| |
| func parseDynamicSecret(s *envoy_admin.SecretsConfigDump_DynamicSecret, state string) (SecretItem, error) { |
| builder := NewSecretItemBuilder() |
| builder.Name(s.Name).State(state) |
| |
| secretTyped := &auth.Secret{} |
| err := s.GetSecret().UnmarshalTo(secretTyped) |
| if err != nil { |
| return SecretItem{}, err |
| } |
| |
| certChainSecret := secretTyped. |
| GetTlsCertificate(). |
| GetCertificateChain(). |
| GetInlineBytes() |
| caDataSecret := secretTyped. |
| GetValidationContext(). |
| GetTrustedCa(). |
| GetInlineBytes() |
| |
| // seems as though the most straightforward way to tell whether this is a root ca or not |
| // is to check whether the inline bytes of the cert chain or the trusted ca field is zero length |
| if len(certChainSecret) > 0 { |
| builder.Data(string(certChainSecret)) |
| } else if len(caDataSecret) > 0 { |
| builder.Data(string(caDataSecret)) |
| } |
| |
| secret, err := builder.Build() |
| if err != nil { |
| return SecretItem{}, fmt.Errorf("error building secret: %v", err) |
| } |
| |
| return secret, nil |
| } |
| |
| func secretMetaFromCert(rawCert []byte) (SecretMeta, error) { |
| block, _ := pem.Decode(rawCert) |
| if block == nil { |
| return SecretMeta{}, fmt.Errorf("failed to parse certificate PEM") |
| } |
| cert, err := x509.ParseCertificate(block.Bytes) |
| if err != nil { |
| return SecretMeta{}, err |
| } |
| var certType string |
| if cert.IsCA { |
| certType = "CA" |
| } else { |
| certType = "Cert Chain" |
| } |
| |
| return SecretMeta{ |
| SerialNumber: fmt.Sprintf("%d", cert.SerialNumber), |
| NotAfter: cert.NotAfter.Format(time.RFC3339), |
| NotBefore: cert.NotBefore.Format(time.RFC3339), |
| Type: certType, |
| }, nil |
| } |