Repository: replicatedhq/kots
Pull request #911 (2020-08-06 20:04:01)

Add ca template functions to dependency graph


Patch:
diff --git a/pkg/template/depgraph.go b/pkg/template/depgraph.go
index fe43ab147..4f662c1c4 100644
--- a/pkg/template/depgraph.go
+++ b/pkg/template/depgraph.go
@@ -11,9 +11,14 @@ import (
 )
 
 type depGraph struct {
-	Dependencies map[string]map[string]struct{}
-	CertItems    map[string]map[string]struct{} // items that use the TLSCert template function. map of certnames to configoptions that provide the certname
-	KeyItems     map[string]map[string]struct{} // items that use the TLSKey template function. map of configoptions to the certnames they use
+	Dependencies        map[string]map[string]struct{}
+	CertItems           map[string]map[string]struct{} // items that use the TLSCert template function. map of certnames to configoptions that provide the certname
+	KeyItems            map[string]map[string]struct{} // items that use the TLSKey template function. map of TLSKey configoptions to the certnames they use
+	CAItems             map[string]map[string]struct{} // items that use the TLSCACert template function. map of canames to configoptions that provide the caname
+	CAFromCertItems     map[string]map[string]struct{} // items that use the TLSCertFromCA template function. map of TLSCertFromCA configoptions to the canames they use
+	CertFromCACertItems map[string]map[string]struct{} // items that use the TLSCertFromCA template function. map of canames and certnames to configoptions that provide the certname
+	CAItemsFromKey      map[string]map[string]struct{} // items that use the TLSKeyFromCA template function. map of TLSKeyFromCA configoptions to the canames they use
+	CACertItemsFromKey  map[string]map[string]struct{} // items that use the TLSKeyFromCA template function. map of TLSKeyFromCA configoptions to the canames and certnames they use
 }
 
 // these config functions are used to add their dependencies to the depGraph
@@ -33,14 +38,32 @@ func (d *depGraph) funcMap(parent string) template.FuncMap {
 		return certName
 	}
 
+	addCAFunc := func(caName string, _ ...string) string {
+		d.AddCA(parent, caName)
+		return caName
+	}
+
+	addCertFromCAFunc := func(caName, certName string, _ ...string) string {
+		d.AddCertFromCA(parent, caName, certName)
+		return certName
+	}
+
+	addKeyFromCAFunc := func(caName, certName string, _ ...string) string {
+		d.AddKeyFromCA(parent, caName, certName)
+		return certName
+	}
+
 	return template.FuncMap{
 		"ConfigOption":          addDepFunc,
 		"ConfigOptionIndex":     addDepFunc,
 		"ConfigOptionData":      addDepFunc,
 		"ConfigOptionEquals":    addDepFunc,
 		"ConfigOptionNotEquals": addDepFunc,
+		"TLSCACert":             addCAFunc,
 		"TLSCert":               addCertFunc,
+		"TLSCertFromCA":         addCertFromCAFunc,
 		"TLSKey":                addKeyFunc,
+		"TLSKeyFromCA":          addKeyFromCAFunc,
 	}
 }
 
@@ -61,25 +84,37 @@ func (d *depGraph) AddDep(source, newDependency string) {
 }
 
 func (d *depGraph) AddCert(source, certName string) {
-	if d.CertItems == nil {
-		d.CertItems = make(map[string]map[string]struct{})
-	}
-	if _, ok := d.CertItems[certName]; !ok {
-		d.CertItems[certName] = make(map[string]struct{})
-	}
-
-	d.CertItems[certName][source] = struct{}{}
+	d.CertItems = addDepGraphItem(d.CertItems, certName, source)
 }
 
 func (d *depGraph) AddKey(source, certName string) {
-	if d.KeyItems == nil {
-		d.KeyItems = make(map[string]map[string]struct{})
+	d.KeyItems = addDepGraphItem(d.KeyItems, source, certName)
+}
+
+func (d *depGraph) AddCA(source, caName string) {
+	d.CAItems = addDepGraphItem(d.CAItems, caName, source)
+}
+
+func (d *depGraph) AddCertFromCA(source, caName, certName string) {
+	d.CAFromCertItems = addDepGraphItem(d.CAFromCertItems, source, caName)
+	d.CertFromCACertItems = addDepGraphItem(d.CertFromCACertItems, caName+certName, source)
+}
+
+func (d *depGraph) AddKeyFromCA(source, caName, certName string) {
+	d.CAItemsFromKey = addDepGraphItem(d.CAItemsFromKey, source, caName)
+	d.CACertItemsFromKey = addDepGraphItem(d.CACertItemsFromKey, source, caName+certName)
+}
+
+func addDepGraphItem(m map[string]map[string]struct{}, key, value string) map[string]map[string]struct{} {
+	if m == nil {
+		m = make(map[string]map[string]struct{})
 	}
-	if _, ok := d.KeyItems[source]; !ok {
-		d.KeyItems[source] = make(map[string]struct{})
+	if _, ok := m[key]; !ok {
+		m[key] = make(map[string]struct{})
 	}
 
-	d.KeyItems[source][certName] = struct{}{}
+	m[key][value] = struct{}{}
+	return m
 }
 
 func (d *depGraph) resolveCertKeys() {
@@ -94,6 +129,39 @@ func (d *depGraph) resolveCertKeys() {
 	}
 }
 
+func (d *depGraph) resolveCACerts() {
+	for source, caNameMap := range d.CAFromCertItems {
+		for caName := range caNameMap {
+			for caProvider := range d.CAItems[caName] {
+				if caProvider != source {
+					d.AddDep(source, caProvider)
+				}
+			}
+		}
+	}
+}
+
+func (d *depGraph) resolveCACertKeys() {
+	for source, caNameMap := range d.CAItemsFromKey {
+		for caName := range caNameMap {
+			for caProvider := range d.CAItems[caName] {
+				if caProvider != source {
+					d.AddDep(source, caProvider)
+				}
+			}
+		}
+	}
+	for source, caCertNameMap := range d.CACertItemsFromKey {
+		for caCertName := range caCertNameMap {
+			for certProvider := range d.CertFromCACertItems[caCertName] {
+				if certProvider != source {
+					d.AddDep(source, certProvider)
+				}
+			}
+		}
+	}
+}
+
 func (d *depGraph) ResolveDep(resolvedDependency string) {
 	for _, depMap := range d.Dependencies {
 		delete(depMap, resolvedDependency)
@@ -168,6 +236,7 @@ func (d *depGraph) ParseConfigGroup(configGroups []kotsv1beta1.ConfigGroup) erro
 	}
 
 	d.resolveCertKeys()
+	d.resolveCACertKeys()
 
 	return nil
 }


Test patch:
diff --git a/pkg/template/depgraph_test.go b/pkg/template/depgraph_test.go
index 7e66771b8..ff2e3fb40 100644
--- a/pkg/template/depgraph_test.go
+++ b/pkg/template/depgraph_test.go
@@ -11,12 +11,15 @@ import (
 )
 
 type depGraphTestCase struct {
-	dependencies   map[string][]string
-	testCerts      map[string][]string
-	testKeys       map[string][]string
-	resolveOrder   []string
-	expectError    bool   //expect an error fetching head nodes
-	expectNotFound string //expect this dependency not to be part of the head nodes
+	dependencies    map[string][]string
+	testCerts       map[string][]string
+	testKeys        map[string][]string
+	testCAs         map[string][]string
+	testCAFromCerts map[string][][2]string
+	testCAFromKeys  map[string][][2]string
+	resolveOrder    []string
+	expectError     bool     //expect an error fetching head nodes
+	expectNotFound  []string //expect these dependencies not to be part of the head nodes
 
 	name string
 }
@@ -64,7 +67,7 @@ func TestDepGraph(t *testing.T) {
 				"foxtrot": {},
 			},
 			resolveOrder:   []string{"alpha", "bravo", "charlie", "delta", "echo", "foxtrot"},
-			expectNotFound: "delta",
+			expectNotFound: []string{"delta"},
 			name:           "unresolved_dependency",
 		},
 		{
@@ -148,7 +151,83 @@ func TestDepGraph(t *testing.T) {
 			},
 			resolveOrder:   []string{"alpha", "bravo", "charlie", "delta", "echo"},
 			name:           "basic_certs_original_order",
-			expectNotFound: "delta",
+			expectNotFound: []string{"delta"},
+		},
+		{
+			dependencies: map[string][]string{
+				"alpha":   {},
+				"bravo":   {"alpha"},
+				"charlie": {"alpha", "bravo"},
+				"delta":   {},
+				"echo":    {},
+			},
+			testCAs: map[string][]string{
+				"echo": {"caA"},
+			},
+			testCAFromCerts: map[string][][2]string{
+				"delta": {{"caA", "certA"}},
+			},
+			testCAFromKeys: map[string][][2]string{
+				"charlie": {{"caA", "certA"}},
+			},
+			resolveOrder: []string{"alpha", "bravo", "echo", "delta", "charlie"},
+			name:         "basic_cacerts",
+		},
+		{
+			dependencies: map[string][]string{
+				"alpha":   {},
+				"bravo":   {"alpha"},
+				"charlie": {"alpha", "bravo"},
+				"delta":   {},
+				"echo":    {},
+			},
+			testCAs: map[string][]string{
+				"echo": {"caA"},
+			},
+			testCAFromCerts: map[string][][2]string{
+				"delta": {{"caA", "certA"}},
+			},
+			testCAFromKeys: map[string][][2]string{
+				"charlie": {{"caA", "certA"}},
+			},
+			resolveOrder:   []string{"alpha", "bravo", "charlie", "delta", "echo"},
+			name:           "basic_cacerts_original_order",
+			expectNotFound: []string{"delta", "charlie"},
+		},
+		{
+			dependencies: map[string][]string{
+				"alpha":   {},
+				"bravo":   {"alpha"},
+				"charlie": {"bravo"},
+				"delta":   {"alpha", "charlie"},
+				"echo":    {},
+			},
+			testCAs: map[string][]string{
+				"echo": {"caA"},
+			},
+			testCAFromKeys: map[string][][2]string{
+				"delta": {{"caA", "certA"}},
+			},
+			resolveOrder: []string{"alpha", "bravo", "charlie", "echo", "delta"},
+			name:         "basic_cacerts_key_only",
+		},
+		{
+			dependencies: map[string][]string{
+				"alpha":   {},
+				"bravo":   {"alpha"},
+				"charlie": {"bravo"},
+				"delta":   {"alpha", "charlie"},
+				"echo":    {},
+			},
+			testCAs: map[string][]string{
+				"echo": {"caA"},
+			},
+			testCAFromKeys: map[string][][2]string{
+				"delta": {{"caA", "certA"}},
+			},
+			resolveOrder:   []string{"alpha", "bravo", "charlie", "delta", "echo"},
+			name:           "basic_cacerts_key_only_original_order",
+			expectNotFound: []string{"delta"},
 		},
 	}
 	for _, test := range tests {
@@ -173,7 +252,24 @@ func TestDepGraph(t *testing.T) {
 					graph.AddKey(source, key)
 				}
 			}
+			for source, caNames := range test.testCAs {
+				for _, caName := range caNames {
+					graph.AddCA(source, caName)
+				}
+			}
+			for source, caCertNames := range test.testCAFromCerts {
+				for _, caCertName := range caCertNames {
+					graph.AddCertFromCA(source, caCertName[0], caCertName[1])
+				}
+			}
+			for source, caCertNames := range test.testCAFromKeys {
+				for _, caCertName := range caCertNames {
+					graph.AddKeyFromCA(source, caCertName[0], caCertName[1])
+				}
+			}
 			graph.resolveCertKeys()
+			graph.resolveCACerts()
+			graph.resolveCACertKeys()
 
 			runGraphTests(t, test, graph)
 		})
@@ -184,7 +280,11 @@ func TestDepGraph(t *testing.T) {
 
 			graph := depGraph{}
 
-			groups := buildTestConfigGroups(test.dependencies, test.testCerts, test.testKeys, "templateStringStart", "templateStringEnd", true)
+			groups := buildTestConfigGroups(
+				test.dependencies, test.testCerts, test.testKeys,
+				test.testCAs, test.testCAFromCerts, test.testCAFromKeys,
+				"templateStringStart", "templateStringEnd", true,
+			)
 
 			err := graph.ParseConfigGroup(groups)
 			require.NoError(t, err)
@@ -195,7 +295,7 @@ func TestDepGraph(t *testing.T) {
 }
 
 // this makes sure that we test with each of the configOption types, in both Value and Default
-func buildTestConfigGroups(dependencies, certs, keys map[string][]string, prefix string, suffix string, rotate bool) []kotsv1beta1.ConfigGroup {
+func buildTestConfigGroups(dependencies, certs, keys, cas map[string][]string, caFromCerts, caFromKeys map[string][][2]string, prefix string, suffix string, rotate bool) []kotsv1beta1.ConfigGroup {
 	group := kotsv1beta1.ConfigGroup{}
 	group.Items = make([]kotsv1beta1.ConfigItem, 0)
 	counter := 0
@@ -237,6 +337,24 @@ func buildTestConfigGroups(dependencies, certs, keys map[string][]string, prefix
 			}
 		}
 
+		if caNames, ok := cas[source]; ok {
+			for _, caName := range caNames {
+				depString += fmt.Sprintf("{{repl TLSCACert \"%s\" }}", caName)
+			}
+		}
+
+		if caCertNames, ok := caFromCerts[source]; ok {
+			for _, caCertName := range caCertNames {
+				depString += fmt.Sprintf("{{repl TLSCertFromCA \"%s\" \"%s\" }}", caCertName[0], caCertName[1])
+			}
+		}
+
+		if caCertNames, ok := caFromKeys[source]; ok {
+			for _, caCertName := range caCertNames {
+				depString += fmt.Sprintf("{{repl TLSKeyFromCA \"%s\" \"%s\" }}", caCertName[0], caCertName[1])
+			}
+		}
+
 		depString += suffix
 
 		if counter%2 == 0 {
@@ -268,7 +386,7 @@ func runGraphTests(t *testing.T, test depGraphTestCase, graph depGraph) {
 
 		require.NoError(t, err, "toResolve: %s", toResolve)
 
-		if test.expectNotFound != "" && toResolve == test.expectNotFound {
+		if stringInSlice(toResolve, test.expectNotFound) {
 			require.NotContains(t, available, toResolve)
 			return
 		}
@@ -286,3 +404,12 @@ func runGraphTests(t *testing.T, test depGraphTestCase, graph depGraph) {
 
 	require.Equal(t, depLen, len(graphCopy.Dependencies))
 }
+
+func stringInSlice(a string, list []string) bool {
+	for _, b := range list {
+		if b == a {
+			return true
+		}
+	}
+	return false
+}