diff --git a/pkg/nsx/services/common/types.go b/pkg/nsx/services/common/types.go index f4fc46914..6f09432ac 100644 --- a/pkg/nsx/services/common/types.go +++ b/pkg/nsx/services/common/types.go @@ -16,6 +16,7 @@ import ( const ( HashLength int = 8 + Base62HashLength int = 6 MaxTagsCount int = 26 MaxTagScopeLength int = 128 MaxTagValueLength int = 256 diff --git a/pkg/nsx/services/securitypolicy/builder_test.go b/pkg/nsx/services/securitypolicy/builder_test.go index 80a18a280..8eacd9d5b 100644 --- a/pkg/nsx/services/securitypolicy/builder_test.go +++ b/pkg/nsx/services/securitypolicy/builder_test.go @@ -1274,8 +1274,8 @@ func Test_BuildSecurityPolicyName(t *testing.T) { }, }, createdFor: common.ResourceTypeNetworkPolicy, - expName: fmt.Sprintf("%s_c64163f0", strings.Repeat("a", 246)), - expId: fmt.Sprintf("%s_fb85d834", strings.Repeat("a", 246)), + expName: fmt.Sprintf("%s_shQDwf", strings.Repeat("a", 248)), + expId: fmt.Sprintf("%s_zT4Byn", strings.Repeat("a", 248)), }, } { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/nsx/services/vpc/builder_test.go b/pkg/nsx/services/vpc/builder_test.go index e6683715b..f52dc4d45 100644 --- a/pkg/nsx/services/vpc/builder_test.go +++ b/pkg/nsx/services/vpc/builder_test.go @@ -171,7 +171,7 @@ func TestBuildNSXVPC(t *testing.T) { }, expVPC: &model.Vpc{ Id: common.String("test-ns-03a2def3-0087-4077-904e-23e4dd788fb7_ecc6eb9f-92b5-4893-b809-e3ebc1fcf59e"), - DisplayName: common.String("test-ns-03a2def3-0087-4077-904e-23e4dd788fb7_f4f0080e"), + DisplayName: common.String("test-ns-03a2def3-0087-4077-904e-23e4dd788fb7_yWOLBB"), PrivateIps: []string{"192.168.3.0/24"}, IpAddressType: common.String("IPV4"), Tags: []model.Tag{ diff --git a/pkg/util/utils.go b/pkg/util/utils.go index fe0169afd..9576bf756 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -9,6 +9,7 @@ import ( "encoding/binary" "errors" "fmt" + "math/big" "net" "strconv" "strings" @@ -31,6 +32,7 @@ import ( const ( wcpSystemResource = "vmware-system-shared-t1" + base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ) var ( @@ -39,44 +41,47 @@ var ( var log = &logger.Log +func truncateLabelHash(data string) string { + return Sha1(data)[:common.HashLength] +} func NormalizeLabels(matchLabels *map[string]string) *map[string]string { newLabels := make(map[string]string) for k, v := range *matchLabels { - newLabels[NormalizeLabelKey(k)] = NormalizeName(v) + newLabels[NormalizeLabelKey(k, truncateLabelHash)] = NormalizeName(v, truncateLabelHash) } return &newLabels } -func NormalizeLabelKey(key string) string { +func NormalizeLabelKey(key string, shaFn func(data string) string) string { if len(key) <= common.MaxTagScopeLength { return key } splitted := strings.Split(key, "/") key = splitted[len(splitted)-1] - return normalizeNameByLimit(key, "", common.MaxTagScopeLength) + return normalizeNameByLimit(key, "", common.MaxTagScopeLength, shaFn) } -func NormalizeName(name string) string { - return normalizeNameByLimit(name, "", common.MaxTagValueLength) +func NormalizeName(name string, shaFn func(data string) string) string { + return normalizeNameByLimit(name, "", common.MaxTagValueLength, shaFn) } -func normalizeNameByLimit(name string, suffix string, limit int) string { +func normalizeNameByLimit(name string, suffix string, limit int, hashFn func(data string) string) string { newName := connectStrings(common.ConnectorUnderline, name, suffix) if len(newName) <= limit { return newName } - var hashString string + hashedTarget := name if len(suffix) > 0 { - hashString = Sha1(suffix) - } else { - hashString = Sha1(name) + hashedTarget = suffix } - nameLength := limit - common.HashLength - 1 + + hashString := hashFn(hashedTarget) + nameLength := limit - len(hashString) - 1 if len(name) < nameLength { nameLength = len(name) } - return strings.Join([]string{name[:nameLength], hashString[:common.HashLength]}, common.ConnectorUnderline) + return strings.Join([]string{name[:nameLength], hashString}, common.ConnectorUnderline) } func NormalizeId(name string) string { @@ -94,10 +99,36 @@ func NormalizeId(name string) string { } func Sha1(data string) string { + sum := getSha1Bytes(data) + return fmt.Sprintf("%x", sum) +} + +func getSha1Bytes(data string) []byte { h := sha1.New() // #nosec G401: not used for security purposes h.Write([]byte(data)) sum := h.Sum(nil) - return fmt.Sprintf("%x", sum) + return sum +} + +// Sha1WithBase62 uses the chars in `base62Chars` to present the hash result on the input data. We now use Sha1 as +// the hash algorithm. +func Sha1WithBase62(data string) string { + sum := getSha1Bytes(data) + value := new(big.Int).SetBytes(sum[:]) + base := big.NewInt(int64(len(base62Chars))) + var result []byte + for value.Cmp(big.NewInt(0)) > 0 { + mod := new(big.Int).Mod(value, base) + result = append(result, base62Chars[mod.Int64()]) + value.Div(value, base) + } + + // Reverse the result because the encoding process generates characters in reverse order + for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 { + result[i], result[j] = result[j], result[i] + } + + return string(result) } func RemoveDuplicateStr(strSlice []string) []string { @@ -339,11 +370,15 @@ func UpdateK8sResourceAnnotation(client client.Client, ctx context.Context, k8sO return nil } +func truncateNameOrIDHash(data string) string { + return Sha1WithBase62(data)[:common.Base62HashLength] +} + // GenerateIDByObject generate string id for NSX resource using the provided Object's name and uid. Note, // this function is used on the resources with VPC scenario, and the provided obj is the K8s CR which is // used to generate the NSX resource. func GenerateIDByObject(obj metav1.Object) string { - return normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), common.MaxIdLength) + return normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), common.MaxIdLength, truncateNameOrIDHash) } // GenerateIDByObjectByLimit generate string id for NSX resource using the provided Object's name and uid, @@ -352,13 +387,13 @@ func GenerateIDByObjectByLimit(obj metav1.Object, limit int) string { if limit == 0 { limit = common.MaxIdLength } - return normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), limit) + return normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), limit, truncateNameOrIDHash) } func GenerateIDByObjectWithSuffix(obj metav1.Object, suffix string) string { limit := common.MaxIdLength limit -= len(suffix) + 1 - return connectStrings(common.ConnectorUnderline, normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), limit), suffix) + return connectStrings(common.ConnectorUnderline, normalizeNameByLimit(obj.GetName(), string(obj.GetUID()), limit, truncateNameOrIDHash), suffix) } // GenerateID generate id for NSX resource, some resources has complex index, so set it type to string @@ -391,7 +426,7 @@ func GenerateTruncName(limit int, resName string, prefix, suffix, project, clust } oldName := generateDisplayName(common.ConnectorUnderline, resName, "", "", project, cluster) if len(oldName) > adjustedLimit { - newName := normalizeNameByLimit(oldName, "", adjustedLimit) + newName := normalizeNameByLimit(oldName, "", adjustedLimit, truncateNameOrIDHash) return generateDisplayName(common.ConnectorUnderline, newName, prefix, suffix, "", "") } return generateDisplayName(common.ConnectorUnderline, resName, prefix, suffix, project, cluster) diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go index c39c19460..cf51ec688 100644 --- a/pkg/util/utils_test.go +++ b/pkg/util/utils_test.go @@ -12,14 +12,16 @@ import ( "strings" "testing" + "github.com/google/uuid" "github.com/stretchr/testify/assert" - - "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" - + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/vmware-tanzu/nsx-operator/pkg/nsx/services/common" ) func TestSha1(t *testing.T) { @@ -28,16 +30,16 @@ func TestSha1(t *testing.T) { func TestNormalizeName(t *testing.T) { shortName := strings.Repeat("a", 256) - assert.Equal(t, NormalizeName(shortName), shortName) + assert.Equal(t, NormalizeName(shortName, truncateLabelHash), shortName) longName := strings.Repeat("a", 257) - assert.Equal(t, NormalizeName(longName), fmt.Sprintf("%s_%s", strings.Repeat("a", 256-common.HashLength-1), "0c103888")) + assert.Equal(t, NormalizeName(longName, truncateLabelHash), fmt.Sprintf("%s_%s", strings.Repeat("a", 256-common.HashLength-1), "0c103888")) } func TestNormalizeLabelKey(t *testing.T) { shortKey := strings.Repeat("a", 128) - assert.Equal(t, NormalizeLabelKey(shortKey), shortKey) + assert.Equal(t, NormalizeLabelKey(shortKey, truncateLabelHash), shortKey) longKey := strings.Repeat("a", 129) + "/def" - assert.Equal(t, NormalizeLabelKey(longKey), "def") + assert.Equal(t, NormalizeLabelKey(longKey, truncateLabelHash), "def") } func TestNormalizeLabels(t *testing.T) { @@ -55,7 +57,7 @@ func TestNormalizeLabels(t *testing.T) { longKey: longValue, }, expectedLabels: &map[string]string{ - "def": NormalizeName(longValue), + "def": NormalizeName(longValue, truncateLabelHash), }, }, { @@ -64,7 +66,7 @@ func TestNormalizeLabels(t *testing.T) { shortKey: longValue, }, expectedLabels: &map[string]string{ - shortKey: NormalizeName(longValue), + shortKey: NormalizeName(longValue, truncateLabelHash), }, }, } @@ -500,7 +502,7 @@ func TestGenerateTruncName(t *testing.T) { project: strings.Repeat("s", 300), cluster: "k8scl-one", }, - want: "sr_k8scl-one_1234-456_ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_e89b45cc_scope", + want: "sr_k8scl-one_1234-456_ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_xbJrtX_scope", }, } for _, tt := range tests { @@ -660,13 +662,13 @@ func TestGenerateIDByObject(t *testing.T) { name: "truncate with hash on uid", obj: &metav1.ObjectMeta{Name: "abcdefg", UID: "b720ee2c-5788-4680-9796-0f93db33d8a9"}, limit: 20, - expID: "abcdefg_df78acb2", + expID: "abcdefg_vSV1eZ", }, { name: "longer name with truncate", obj: &metav1.ObjectMeta{Name: strings.Repeat("a", 256), UID: "b720ee2c-5788-4680-9796-0f93db33d8a9"}, limit: 0, - expID: fmt.Sprintf("%s_df78acb2", strings.Repeat("a", 246)), + expID: fmt.Sprintf("%s_vSV1eZ", strings.Repeat("a", 248)), }, } { t.Run(tc.name, func(t *testing.T) { @@ -701,7 +703,7 @@ func TestGenerateIDByObjectWithSuffix(t *testing.T) { obj: &metav1.ObjectMeta{Name: strings.Repeat("a", 256), UID: "b720ee2c-5788-4680-9796-0f93db33d8a9"}, limit: 0, suffix: "28e85c0b-21e4-4cab-b1c3-597639dfe752", - expID: fmt.Sprintf("%s_df78acb2_28e85c0b-21e4-4cab-b1c3-597639dfe752", strings.Repeat("a", 209)), + expID: fmt.Sprintf("%s_vSV1eZ_28e85c0b-21e4-4cab-b1c3-597639dfe752", strings.Repeat("a", 211)), }, } { t.Run(tc.name, func(t *testing.T) { @@ -734,3 +736,20 @@ func TestConnectStrings(t *testing.T) { expString = fmt.Sprintf("%s%s%d", string1, common.ConnectorUnderline, int2) assert.Equal(t, connectString, expString) } + +func TestNewSha1(t *testing.T) { + assert.Equal(t, "ffN5UpVkkQYbocYDKFXOAMN4AsA", Sha1WithBase62("name")) + assert.Equal(t, "hZBZpydbX1XIFhgs9m6Lt2for9m", Sha1WithBase62("namee")) + + allowedChars := sets.New[rune]() + for _, c := range base62Chars { + allowedChars.Insert(c) + } + randUID, err := uuid.NewRandom() + require.NoError(t, err) + hashString := Sha1WithBase62(randUID.String()) + // Verify all chars in the hash string are contained in base62Chars. + for _, c := range hashString { + assert.True(t, allowedChars.Has(c)) + } +}