Skip to content

Commit 62756e3

Browse files
authored
feat(codegen): Add support for more hashing types and hide them within private state (#520)
1 parent dd23445 commit 62756e3

File tree

12 files changed

+795
-218
lines changed

12 files changed

+795
-218
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package provider
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
type HashingType string
9+
10+
const (
11+
HashingSoloType HashingType = "solo"
12+
HashingCustomType HashingType = "custom"
13+
)
14+
15+
type EncryptedValue struct {
16+
HashingType HashingType `json:"hashing_type"`
17+
Encrypted *string `json:"encrypted,omitempty"`
18+
Plaintext *string `json:"plaintext,omitempty"`
19+
}
20+
21+
type EncryptedValuesManager struct {
22+
preferServerState bool `json:"-"`
23+
EncryptedValues map[string]*EncryptedValue `json:"values"`
24+
}
25+
26+
type EncryptedHashingTypeMismatchError struct {
27+
key string
28+
expected HashingType
29+
actual HashingType
30+
}
31+
32+
func (o EncryptedHashingTypeMismatchError) Error() string {
33+
return fmt.Sprintf("unexpected hashing type for key '%s': %s != %s", o.key, o.expected, o.actual)
34+
}
35+
36+
func NewEncryptedValuesManager(payload []byte, preferServerState bool) (*EncryptedValuesManager, error) {
37+
manager := &EncryptedValuesManager{
38+
preferServerState: preferServerState,
39+
}
40+
41+
if payload != nil {
42+
err := json.Unmarshal(payload, manager)
43+
if err != nil {
44+
return nil, err
45+
}
46+
} else {
47+
manager.EncryptedValues = make(map[string]*EncryptedValue)
48+
}
49+
50+
return manager, nil
51+
}
52+
53+
func (o EncryptedValuesManager) PreferServerState() bool {
54+
return o.preferServerState
55+
}
56+
57+
func (o *EncryptedValuesManager) StorePlaintextValue(key string, hashing_type HashingType, value string) error {
58+
values, found := o.EncryptedValues[key]
59+
if !found {
60+
values = &EncryptedValue{
61+
Plaintext: &value,
62+
HashingType: hashing_type,
63+
}
64+
o.EncryptedValues[key] = values
65+
} else if values.HashingType != hashing_type {
66+
return EncryptedHashingTypeMismatchError{
67+
key: key,
68+
expected: hashing_type,
69+
actual: values.HashingType,
70+
}
71+
} else {
72+
values.Plaintext = &value
73+
}
74+
75+
return nil
76+
}
77+
78+
func (o *EncryptedValuesManager) StoreEncryptedValue(key string, hashing_type HashingType, value string) error {
79+
values, found := o.EncryptedValues[key]
80+
if !found {
81+
values = &EncryptedValue{
82+
Encrypted: &value,
83+
HashingType: hashing_type,
84+
}
85+
o.EncryptedValues[key] = values
86+
} else if values.HashingType != hashing_type {
87+
return EncryptedHashingTypeMismatchError{
88+
key: key,
89+
expected: hashing_type,
90+
actual: values.HashingType,
91+
}
92+
} else {
93+
values.Encrypted = &value
94+
}
95+
96+
return nil
97+
}
98+
99+
func (o EncryptedValuesManager) GetPlaintextValue(key string) (string, bool) {
100+
if values, found := o.EncryptedValues[key]; !found {
101+
return "", false
102+
} else if values.Plaintext == nil {
103+
return "", false
104+
} else {
105+
return *values.Plaintext, true
106+
}
107+
}
108+
109+
func (o EncryptedValuesManager) GetEncryptedValue(key string) (string, bool) {
110+
if values, found := o.EncryptedValues[key]; !found {
111+
return "", false
112+
} else if values.Encrypted == nil {
113+
return "", false
114+
} else {
115+
return *values.Encrypted, true
116+
}
117+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package provider
2+
3+
import (
4+
"encoding/json"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
var _ = Describe("EncryptedValuesManager", func() {
11+
payload := []byte(`{"values":{"/attr-1":{"hashing_type":"solo","encrypted":"$enc$value","plaintext":"value"}}}`)
12+
Context("when creating encrypted values manager from existing payload", func() {
13+
It("should return correct values", func() {
14+
ev, err := NewEncryptedValuesManager(payload, false)
15+
Expect(err).ToNot(HaveOccurred())
16+
17+
value, found := ev.GetPlaintextValue("/attr-1")
18+
Expect(found).To(BeTrue())
19+
Expect(value).To(Equal("value"))
20+
21+
value, found = ev.GetEncryptedValue("/attr-1")
22+
Expect(found).To(BeTrue())
23+
Expect(value).To(Equal("$enc$value"))
24+
25+
marshalled, err := json.Marshal(ev)
26+
Expect(err).ToNot(HaveOccurred())
27+
Expect(marshalled).To(Equal(payload))
28+
})
29+
})
30+
Context("when creating encrypted values manager with no existing payload", func() {
31+
var payload []byte
32+
Context("and inserting plaintext and encrypted values for a given xpath", func() {
33+
It("should return expected values back when requested", func() {
34+
ev, err := NewEncryptedValuesManager(payload, false)
35+
Expect(err).ToNot(HaveOccurred())
36+
37+
err = ev.StorePlaintextValue("/attr-1", HashingSoloType, "value")
38+
Expect(err).ToNot(HaveOccurred())
39+
40+
err = ev.StoreEncryptedValue("/attr-1", HashingSoloType, "$enc$value")
41+
Expect(err).ToNot(HaveOccurred())
42+
43+
value, found := ev.GetPlaintextValue("/attr-1")
44+
Expect(found).To(BeTrue())
45+
Expect(value).To(Equal("value"))
46+
47+
value, found = ev.GetEncryptedValue("/attr-1")
48+
Expect(found).To(BeTrue())
49+
Expect(value).To(Equal("$enc$value"))
50+
51+
expected := []byte(`{"values":{"/attr-1":{"hashing_type":"solo","encrypted":"$enc$value","plaintext":"value"}}}`)
52+
marshalled, err := json.Marshal(ev)
53+
Expect(err).ToNot(HaveOccurred())
54+
Expect(marshalled).To(Equal(expected))
55+
})
56+
})
57+
})
58+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package provider_test
2+
3+
import (
4+
"log/slog"
5+
"testing"
6+
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
)
10+
11+
func TestProvider(t *testing.T) {
12+
handler := slog.NewTextHandler(GinkgoWriter, &slog.HandlerOptions{
13+
Level: slog.LevelDebug,
14+
})
15+
slog.SetDefault(slog.New(handler))
16+
RegisterFailHandler(Fail)
17+
RunSpecs(t, "Provider Suite")
18+
}

assets/terraform/internal/provider/tools.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,53 @@ func ProviderParamDescription(desc, defaultValue, envName, jsonName string) stri
5757

5858
return b.String()
5959
}
60+
61+
type AncestorType string
62+
63+
const (
64+
AncestorObjectEntry AncestorType = "object-entry"
65+
AncestorListEntry AncestorType = "list-entry"
66+
)
67+
68+
type Ancestor interface {
69+
AncestorName() string
70+
EntryName() *string
71+
}
72+
73+
type XpathAncestorError struct {
74+
name string
75+
message string
76+
}
77+
78+
func (o XpathAncestorError) Error() string {
79+
message := o.message
80+
message += fmt.Sprintf(": %s", o.name)
81+
return message
82+
}
83+
84+
func CreateXpathForAttributeWithAncestors(ancestors []Ancestor, attribute string) (string, error) {
85+
var xpath []string
86+
87+
createXpathElements := func(attr Ancestor) ([]string, error) {
88+
elts := []string{"/" + attr.AncestorName()}
89+
name := attr.EntryName()
90+
if name != nil {
91+
elts = append(elts, fmt.Sprintf("/entry[@name=\"%s\"]", *name))
92+
93+
}
94+
95+
return elts, nil
96+
}
97+
98+
for _, elt := range ancestors {
99+
xpathElts, err := createXpathElements(elt)
100+
if err != nil {
101+
return "", err
102+
}
103+
104+
xpath = append(xpath, xpathElts...)
105+
}
106+
107+
xpath = append(xpath, "/"+attribute)
108+
return strings.Join(xpath, ""), nil
109+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package provider_test
2+
3+
import (
4+
. "github.com/onsi/ginkgo/v2"
5+
. "github.com/onsi/gomega"
6+
7+
"github.com/PaloAltoNetworks/terraform-provider-panos/internal/provider"
8+
)
9+
10+
type AncestorMock struct {
11+
name string
12+
entryName string
13+
}
14+
15+
func (o AncestorMock) AncestorName() string {
16+
return o.name
17+
}
18+
19+
func (o AncestorMock) EntryName() *string {
20+
if o.entryName == "" {
21+
return nil
22+
}
23+
return &o.entryName
24+
}
25+
26+
var _ = Describe("CreateXpathForParameterWithAncestors", func() {
27+
Context("When no ancestors are provided", func() {
28+
It("should generate a single element xpath", func() {
29+
var ancestors []provider.Ancestor
30+
31+
xpath, err := provider.CreateXpathForAttributeWithAncestors(ancestors, "attr-1")
32+
Expect(err).ToNot(HaveOccurred())
33+
Expect(xpath).To(Equal("/attr-1"))
34+
})
35+
})
36+
Context("When single ancestor of nested type is provided", func() {
37+
It("should generate a single element xpath", func() {
38+
ancestors := []provider.Ancestor{
39+
&AncestorMock{
40+
name: "attr-1",
41+
},
42+
}
43+
44+
xpath, err := provider.CreateXpathForAttributeWithAncestors(ancestors, "attr-2")
45+
Expect(err).ToNot(HaveOccurred())
46+
Expect(xpath).To(Equal("/attr-1/attr-2"))
47+
})
48+
})
49+
Context("When multiple ancestors are provided", func() {
50+
Context("and one of ancestors is a list", func() {
51+
It("should generate a single element xpath", func() {
52+
ancestors := []provider.Ancestor{
53+
&AncestorMock{
54+
name: "attr-1",
55+
},
56+
&AncestorMock{
57+
name: "attr-2",
58+
entryName: "element-1",
59+
},
60+
}
61+
62+
xpath, err := provider.CreateXpathForAttributeWithAncestors(ancestors, "attr-3")
63+
Expect(err).ToNot(HaveOccurred())
64+
Expect(xpath).To(Equal(`/attr-1/attr-2/entry[@name="element-1"]/attr-3`))
65+
})
66+
})
67+
68+
})
69+
})

assets/terraform/test/resource_ntp_settings_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,25 @@ func TestAccNtpSettings(t *testing.T) {
9898
),
9999
},
100100
},
101+
{
102+
Config: ntpSettingsConfig4,
103+
ConfigVariables: map[string]config.Variable{
104+
"location": location,
105+
},
106+
ConfigStateChecks: []statecheck.StateCheck{
107+
statecheck.ExpectKnownValue(
108+
"panos_ntp_settings.settings",
109+
tfjsonpath.New("ntp_servers").
110+
AtMapKey("secondary_ntp_server").
111+
AtMapKey("authentication_type").
112+
AtMapKey("symmetric_key").
113+
AtMapKey("algorithm").
114+
AtMapKey("md5").
115+
AtMapKey("authentication_key"),
116+
knownvalue.StringExact("83d043db4fdfe6882fb7f01a09d92b11"),
117+
),
118+
},
119+
},
101120
},
102121
})
103122
}
@@ -178,3 +197,41 @@ resource "panos_ntp_settings" "settings" {
178197
}
179198
}
180199
`
200+
201+
const ntpSettingsConfig4 = `
202+
variable "location" { type = map }
203+
204+
resource "panos_ntp_settings" "settings" {
205+
location = var.location
206+
207+
ntp_servers = {
208+
primary_ntp_server = {
209+
ntp_server_address = "172.16.0.1"
210+
authentication_type = {
211+
symmetric_key = {
212+
key_id = 1
213+
algorithm = {
214+
sha1 = {
215+
authentication_key = "da39a3ee5e6b4b0d3255bfef95601890afd80709"
216+
}
217+
}
218+
}
219+
}
220+
}
221+
222+
secondary_ntp_server = {
223+
ntp_server_address = "172.16.0.2"
224+
authentication_type = {
225+
symmetric_key = {
226+
key_id = 1
227+
algorithm = {
228+
md5 = {
229+
authentication_key = "83d043db4fdfe6882fb7f01a09d92b11"
230+
}
231+
}
232+
}
233+
}
234+
}
235+
}
236+
}
237+
`

0 commit comments

Comments
 (0)