Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to include c2p authority #34

Merged
merged 9 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 97 additions & 58 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,29 @@ import (
"github.com/google/uuid"
)

const tdURI = "trafficdirector.googleapis.com:443"
const (
tdURI = "trafficdirector.googleapis.com:443"
c2pAuthority = "traffic-director-c2p.xds.googleapis.com"
)

var (
xdsServerUri = flag.String("xds-server-uri", tdURI, "override of server uri, for testing")
outputName = flag.String("output", "-", "output file name")
gcpProjectNumber = flag.Int64("gcp-project-number", 0, "the gcp project number. If unknown, can be found via 'gcloud projects list'")
vpcNetworkName = flag.String("vpc-network-name", "default", "VPC network name")
localityZone = flag.String("locality-zone", "", "the locality zone to use, instead of retrieving it from the metadata server. Useful when not running on GCP and/or for testing")
ignoreResourceDeletion = flag.Bool("ignore-resource-deletion-experimental", false, "assume missing resources notify operators when using Traffic Director, as in gRFC A53. This is not currently the case. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
includeV3Features = flag.Bool("include-v3-features-experimental", true, "whether or not to generate configs which works with the xDS v3 implementation in TD. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
includePSMSecurity = flag.Bool("include-psm-security-experimental", true, "whether or not to generate config required for PSM security. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
secretsDir = flag.String("secrets-dir", "/var/run/secrets/workload-spiffe-credentials", "path to a directory containing TLS certificates and keys required for PSM security")
includeDeploymentInfo = flag.Bool("include-deployment-info-experimental", false, "whether or not to generate config which contains deployment related information. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
gkeClusterName = flag.String("gke-cluster-name-experimental", "", "GKE cluster name to use, instead of retrieving it from the metadata server. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
gkePodName = flag.String("gke-pod-name-experimental", "", "GKE pod name to use, instead of reading it from $HOSTNAME or /etc/hostname file. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
gkeNamespace = flag.String("gke-namespace-experimental", "", "GKE namespace to use. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
gceVM = flag.String("gce-vm-experimental", "", "GCE VM name to use, instead of reading it from the metadata server. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
configMesh = flag.String("config-mesh-experimental", "", "Dictates which Mesh resource to use. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
includeFederationSupport = flag.Bool("include-federation-support-experimental", false, "whether or not to generate configs required for xDS Federation. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
xdsServerUri = flag.String("xds-server-uri", tdURI, "override of server uri, for testing")
outputName = flag.String("output", "-", "output file name")
gcpProjectNumber = flag.Int64("gcp-project-number", 0, "the gcp project number. If unknown, can be found via 'gcloud projects list'")
vpcNetworkName = flag.String("vpc-network-name", "default", "VPC network name")
localityZone = flag.String("locality-zone", "", "the locality zone to use, instead of retrieving it from the metadata server. Useful when not running on GCP and/or for testing")
ignoreResourceDeletion = flag.Bool("ignore-resource-deletion-experimental", false, "assume missing resources notify operators when using Traffic Director, as in gRFC A53. This is not currently the case. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
includeV3Features = flag.Bool("include-v3-features-experimental", true, "whether or not to generate configs which works with the xDS v3 implementation in TD. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
includePSMSecurity = flag.Bool("include-psm-security-experimental", true, "whether or not to generate config required for PSM security. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
secretsDir = flag.String("secrets-dir", "/var/run/secrets/workload-spiffe-credentials", "path to a directory containing TLS certificates and keys required for PSM security")
includeDeploymentInfo = flag.Bool("include-deployment-info-experimental", false, "whether or not to generate config which contains deployment related information. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
gkeClusterName = flag.String("gke-cluster-name-experimental", "", "GKE cluster name to use, instead of retrieving it from the metadata server. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
gkePodName = flag.String("gke-pod-name-experimental", "", "GKE pod name to use, instead of reading it from $HOSTNAME or /etc/hostname file. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
gkeNamespace = flag.String("gke-namespace-experimental", "", "GKE namespace to use. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
gceVM = flag.String("gce-vm-experimental", "", "GCE VM name to use, instead of reading it from the metadata server. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
configMesh = flag.String("config-mesh-experimental", "", "Dictates which Mesh resource to use. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
includeFederationSupport = flag.Bool("include-federation-support-experimental", false, "whether or not to generate configs required for xDS Federation. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
includeDirectPathAuthority = flag.Bool("include-directpath-authority-experimental", false, "whether or not to include DirectPath TD authority for xDS Federation. Ignored if not used with include-federation-support-experimental flag. This flag is EXPERIMENTAL and may be changed or removed in a later release.")
)

func main() {
Expand Down Expand Up @@ -123,19 +127,21 @@ func main() {
}

input := configInput{
xdsServerUri: *xdsServerUri,
gcpProjectNumber: *gcpProjectNumber,
vpcNetworkName: *vpcNetworkName,
ip: ip,
zone: zone,
ignoreResourceDeletion: *ignoreResourceDeletion,
includeV3Features: *includeV3Features,
includePSMSecurity: *includePSMSecurity,
secretsDir: *secretsDir,
metadataLabels: nodeMetadata,
deploymentInfo: deploymentInfo,
configMesh: *configMesh,
includeFederationSupport: *includeFederationSupport,
xdsServerUri: *xdsServerUri,
gcpProjectNumber: *gcpProjectNumber,
vpcNetworkName: *vpcNetworkName,
ip: ip,
zone: zone,
ignoreResourceDeletion: *ignoreResourceDeletion,
includeV3Features: *includeV3Features,
includePSMSecurity: *includePSMSecurity,
secretsDir: *secretsDir,
metadataLabels: nodeMetadata,
deploymentInfo: deploymentInfo,
configMesh: *configMesh,
includeFederationSupport: *includeFederationSupport,
includeDirectPathAuthority: *includeDirectPathAuthority,
ipv6Capable: isIPv6Capable(),
}

if err := validate(input); err != nil {
Expand Down Expand Up @@ -176,19 +182,21 @@ func main() {
}

type configInput struct {
xdsServerUri string
gcpProjectNumber int64
vpcNetworkName string
ip string
zone string
ignoreResourceDeletion bool
includeV3Features bool
includePSMSecurity bool
secretsDir string
metadataLabels map[string]string
deploymentInfo map[string]string
configMesh string
includeFederationSupport bool
xdsServerUri string
gcpProjectNumber int64
vpcNetworkName string
ip string
zone string
ignoreResourceDeletion bool
includeV3Features bool
includePSMSecurity bool
secretsDir string
metadataLabels map[string]string
deploymentInfo map[string]string
configMesh string
includeFederationSupport bool
includeDirectPathAuthority bool
ipv6Capable bool
}

func validate(in configInput) error {
Expand All @@ -202,14 +210,7 @@ func validate(in configInput) error {

func generate(in configInput) ([]byte, error) {
c := &config{
XdsServers: []server{
{
ServerUri: in.xdsServerUri,
ChannelCreds: []creds{
{Type: "google_default"},
},
},
},
XdsServers: generateServerConfigsFromInputs(in.xdsServerUri, in),
Node: &node{
Id: uuid.New().String() + "~" + in.ip,
Cluster: "cluster", // unused by TD
Expand Down Expand Up @@ -239,7 +240,6 @@ func generate(in configInput) ([]byte, error) {
// xDS v2 implementation in TD expects the IP address to be encoded in the
// id field while the v3 implementation expects this in the metadata.
c.Node.Metadata["INSTANCE_IP"] = in.ip
c.XdsServers[0].ServerFeatures = append(c.XdsServers[0].ServerFeatures, "xds_v3")
}
if in.includePSMSecurity {
c.CertificateProviders = map[string]certificateProviderConfig{
Expand All @@ -257,21 +257,25 @@ func generate(in configInput) ([]byte, error) {
}
c.ServerListenerResourceNameTemplate = "grpc/server?xds.resource.listening_address=%s"
}
if in.ignoreResourceDeletion {
c.XdsServers[0].ServerFeatures = append(c.XdsServers[0].ServerFeatures, "ignore_resource_deletion")
}
if in.deploymentInfo != nil {
c.Node.Metadata["TRAFFIC_DIRECTOR_CLIENT_ENVIRONMENT"] = in.deploymentInfo
}

if in.includeFederationSupport {
// Authorities with an empty server config will end up using
// the top-level server config. For more details, see:
// https://github.com/grpc/proposal/blob/master/A47-xds-federation.md#bootstrap-config-changes.
c.Authorities = map[string]struct{}{
c.Authorities = map[string]Authority{
tdURI: {},
"": {},
}
if in.includeDirectPathAuthority {
c.Authorities[c2pAuthority] = Authority{
XdsServers: generateServerConfigsFromInputs("dns:///directpath-pa.googleapis.com", in),
}
if in.ipv6Capable {
c.Node.Metadata["TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE"] = true
}
}
}

return json.MarshalIndent(c, "", " ")
Expand Down Expand Up @@ -347,6 +351,13 @@ func getVMName() string {
return string(vm)
}

// isIPv6Capable returns true if the VM is configured with an IPv6 address.
// This will contact the metadata server to retrieve this information.
func isIPv6Capable() bool {
_, err := getFromMetadata("http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ipv6s")
return err == nil
}

func getFromMetadata(urlStr string) ([]byte, error) {
parsedUrl, err := url.Parse(urlStr)
if err != nil {
Expand All @@ -371,12 +382,15 @@ func getFromMetadata(urlStr string) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("failed reading from metadata server: %w", err)
}
if code := resp.StatusCode; code < 200 || code > 299 {
return nil, fmt.Errorf("metadata server returned status code %d for url %q", code, parsedUrl)
}
return body, nil
}

type config struct {
XdsServers []server `json:"xds_servers,omitempty"`
Authorities map[string]struct{} `json:"authorities,omitempty"`
Authorities map[string]Authority `json:"authorities,omitempty"`
Node *node `json:"node,omitempty"`
CertificateProviders map[string]certificateProviderConfig `json:"certificate_providers,omitempty"`
ServerListenerResourceNameTemplate string `json:"server_listener_resource_name_template,omitempty"`
Expand All @@ -388,6 +402,31 @@ type server struct {
ServerFeatures []string `json:"server_features,omitempty"`
}

func generateServerConfigsFromInputs(serverUri string, in configInput) []server {
s := server{
ServerUri: serverUri,
ChannelCreds: []creds{
{Type: "google_default"},
},
}
if in.includeV3Features {
s.ServerFeatures = append(s.ServerFeatures, "xds_v3")
}
if in.ignoreResourceDeletion {
s.ServerFeatures = append(s.ServerFeatures, "ignore_resource_deletion")
}

return []server{s}
}

// Authority is the configuration corresponding to an authority name in the map.
//
// For more details, see:
// https://github.com/grpc/proposal/blob/master/A47-xds-federation.md#bootstrap-config-changes
type Authority struct {
XdsServers []server `json:"xds_servers,omitempty"`
}

type creds struct {
Type string `json:"type,omitempty"`
Config interface{} `json:"config,omitempty"`
Expand Down
104 changes: 104 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,67 @@ func TestGenerate(t *testing.T) {
"zone": "uscentral-5"
}
}
}`,
},
{
desc: "happy case with v3 defaults and federation support enabled and c2p authority included",
input: configInput{
xdsServerUri: "example.com:443",
gcpProjectNumber: 123456789012345,
vpcNetworkName: "thedefault",
ip: "10.9.8.7",
zone: "uscentral-5",
includeV3Features: true,
includeFederationSupport: true,
includeDirectPathAuthority: true,
ipv6Capable: true,
},
wantOutput: `{
"xds_servers": [
{
"server_uri": "example.com:443",
"channel_creds": [
{
"type": "google_default"
}
],
"server_features": [
"xds_v3"
]
}
],
"authorities": {
"": {},
"traffic-director-c2p.xds.googleapis.com": {
"xds_servers": [
{
"server_uri": "dns:///directpath-pa.googleapis.com",
"channel_creds": [
{
"type": "google_default"
}
],
"server_features": [
"xds_v3"
]
}
]
},
"trafficdirector.googleapis.com:443": {}
},
"node": {
"id": "projects/123456789012345/networks/thedefault/nodes/9566c74d-1003-4c4d-bbbb-0407d1e2c649",
"cluster": "cluster",
"metadata": {
"INSTANCE_IP": "10.9.8.7",
"TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE": true,
"TRAFFICDIRECTOR_GCP_PROJECT_NUMBER": "123456789012345",
"TRAFFICDIRECTOR_NETWORK_NAME": "thedefault"
},
"locality": {
"zone": "uscentral-5"
}
}
}`,
},
}
Expand Down Expand Up @@ -544,6 +605,49 @@ func TestGetVMName(t *testing.T) {
}
}

func TestCheckIPv6Capable(t *testing.T) {
tests := []struct {
desc string
httpHandler func(http.ResponseWriter, *http.Request)
wantOutput bool
}{
{
desc: "v6 enabled",
httpHandler: func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Metadata-Flavor") != "Google" {
http.Error(w, "Missing Metadata-Flavor", 403)
return
}
w.Write([]byte("6970:7636:2061:6464:7265:7373:2062:6162"))
},
wantOutput: true,
},
{
desc: "v6 not enabled",
httpHandler: func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not Found", 404)
return
},
wantOutput: false,
},
}

for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ipv6s", test.httpHandler)
server := httptest.NewServer(mux)
defer server.Close()
overrideHTTP(server)
if got := isIPv6Capable(); got != test.wantOutput {
t.Fatalf("isIPv6Capable() = %t, want: %t", got, test.wantOutput)
}

})
}

}

func overrideHTTP(s *httptest.Server) {
http.DefaultTransport = &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
Expand Down