tanmayrauth commented on code in PR #1493: URL: https://github.com/apache/iceberg-go/pull/1493#discussion_r3608919911
########## encryption/kms_registry.go: ########## @@ -0,0 +1,120 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 encryption + +import ( + "errors" + "fmt" + "maps" + "slices" + "sync" +) + +// Catalog/table property keys used to select and configure a KeyManagementClient. +const ( + // KMSTypeKey selects a built-in KeyManagementClient implementation + // registered via [RegisterKMS] (e.g. "memory"). + KMSTypeKey = "encryption.kms-type" + + // KMSImplKey selects a custom KeyManagementClient implementation by a + // name registered via [RegisterKMS]. It is an alternative to + // [KMSTypeKey] for vendors that ship their own KMS integration under a + // distinct name; the two properties share the same registry and + // resolution mechanism. + KMSImplKey = "encryption.kms-impl" Review Comment: kms-type and kms-impl resolve the same registry by the same short-name lookup, so they're functionally identical — and that's novel here: nothing else in the repo uses a -impl property, and the precedent (catalog.Load reads a single "type"; the io scheme registry) is one short key. In Iceberg generally -impl means a fully-qualified implementation, distinct from the short -type alias, so someone migrating a config that sets encryption.kms-impl=com.acme.AcmeKmsClient gets ErrKMSTypeNotFound because Go looks it up as a registered short name. Since nothing consumes this yet, either drop kms-impl and keep only kms-type (matching the repo's single-key precedent), or keep both and make the doc explicit that kms-impl is a registered short name, not an FQCN — the "a custom implementation" wording at 34-35 currently reads like the Java FQCN meaning. ########## encryption/kms_registry.go: ########## @@ -0,0 +1,120 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 encryption + +import ( + "errors" + "fmt" + "maps" + "slices" + "sync" +) + +// Catalog/table property keys used to select and configure a KeyManagementClient. +const ( + // KMSTypeKey selects a built-in KeyManagementClient implementation + // registered via [RegisterKMS] (e.g. "memory"). + KMSTypeKey = "encryption.kms-type" + + // KMSImplKey selects a custom KeyManagementClient implementation by a + // name registered via [RegisterKMS]. It is an alternative to + // [KMSTypeKey] for vendors that ship their own KMS integration under a + // distinct name; the two properties share the same registry and + // resolution mechanism. + KMSImplKey = "encryption.kms-impl" +) + +// ErrKMSTypeNotFound is returned by [LoadKeyManagementClient] when the +// requested kmsType has not been registered via [RegisterKMS]. +var ErrKMSTypeNotFound = errors.New("encryption: kms type not found") + +// KMSFactory creates a [KeyManagementClient] from catalog/table properties. +// props is the full property map (typically catalog properties); factories +// should read whichever keys they need (e.g. key material, endpoints, +// credentials) directly from it. +type KMSFactory func(props map[string]string) (KeyManagementClient, error) + +var ( + kmsRegMutex sync.RWMutex + kmsRegistry = map[string]KMSFactory{} +) + +// RegisterKMS adds a new named [KMSFactory] to the registry so it can later +// be selected via the [KMSTypeKey] or [KMSImplKey] catalog property. It +// panics if factory is nil or if name is already registered. +func RegisterKMS(name string, factory KMSFactory) { + if factory == nil { + panic("encryption: RegisterKMS factory is nil") + } + + kmsRegMutex.Lock() + defer kmsRegMutex.Unlock() + + if _, dup := kmsRegistry[name]; dup { + panic("encryption: RegisterKMS called twice for name " + name) + } + kmsRegistry[name] = factory +} + +// UnregisterKMS removes the requested named factory from the registry. +func UnregisterKMS(name string) { + kmsRegMutex.Lock() + defer kmsRegMutex.Unlock() + delete(kmsRegistry, name) +} + +// GetRegisteredKMSNames returns the names of all currently registered KMS +// factories. +func GetRegisteredKMSNames() []string { + kmsRegMutex.RLock() + defer kmsRegMutex.RUnlock() + + return slices.Collect(maps.Keys(kmsRegistry)) +} + +// LoadKeyManagementClient resolves and constructs a [KeyManagementClient] +// from props. It looks up the KMS name under [KMSTypeKey] first, falling +// back to [KMSImplKey] if present; it returns an error wrapping +// [ErrKMSTypeNotFound] if neither property is set or the named KMS has not +// been registered via [RegisterKMS]. +func LoadKeyManagementClient(props map[string]string) (KeyManagementClient, error) { + name := props[KMSTypeKey] + if name == "" { + name = props[KMSImplKey] + } + + if name == "" { + return nil, fmt.Errorf("%w: neither %q nor %q is set", ErrKMSTypeNotFound, KMSTypeKey, KMSImplKey) + } + + kmsRegMutex.RLock() + factory, ok := kmsRegistry[name] + kmsRegMutex.RUnlock() + + if !ok { + return nil, fmt.Errorf("%w: %q", ErrKMSTypeNotFound, name) + } + + return factory(props) Review Comment: Nothing verifies props actually reaches the factory here — every test registers a factory that ignores its argument. The whole point of KMSFactory(props) is that real KMS factories read key material/region/endpoints out of props, so a future cleanup that filtered the map or passed nil before this call would compile, pass the whole suite, and silently hand an AWS/GCP factory an empty map — a factory reading encryption.kms.region would build a wrong-region client or fail at construction, uncaught. Please add a test that registers a factory recording its received props and assert it equals the input (include a sentinel key like encryption.kms.region). ########## encryption/kms_registry.go: ########## @@ -0,0 +1,120 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 encryption + +import ( + "errors" + "fmt" + "maps" + "slices" + "sync" +) + +// Catalog/table property keys used to select and configure a KeyManagementClient. +const ( + // KMSTypeKey selects a built-in KeyManagementClient implementation + // registered via [RegisterKMS] (e.g. "memory"). + KMSTypeKey = "encryption.kms-type" + + // KMSImplKey selects a custom KeyManagementClient implementation by a + // name registered via [RegisterKMS]. It is an alternative to + // [KMSTypeKey] for vendors that ship their own KMS integration under a + // distinct name; the two properties share the same registry and + // resolution mechanism. + KMSImplKey = "encryption.kms-impl" +) + +// ErrKMSTypeNotFound is returned by [LoadKeyManagementClient] when the +// requested kmsType has not been registered via [RegisterKMS]. +var ErrKMSTypeNotFound = errors.New("encryption: kms type not found") + +// KMSFactory creates a [KeyManagementClient] from catalog/table properties. +// props is the full property map (typically catalog properties); factories +// should read whichever keys they need (e.g. key material, endpoints, +// credentials) directly from it. +type KMSFactory func(props map[string]string) (KeyManagementClient, error) + +var ( + kmsRegMutex sync.RWMutex + kmsRegistry = map[string]KMSFactory{} +) + +// RegisterKMS adds a new named [KMSFactory] to the registry so it can later +// be selected via the [KMSTypeKey] or [KMSImplKey] catalog property. It +// panics if factory is nil or if name is already registered. +func RegisterKMS(name string, factory KMSFactory) { + if factory == nil { + panic("encryption: RegisterKMS factory is nil") + } + + kmsRegMutex.Lock() + defer kmsRegMutex.Unlock() + + if _, dup := kmsRegistry[name]; dup { + panic("encryption: RegisterKMS called twice for name " + name) + } + kmsRegistry[name] = factory +} + +// UnregisterKMS removes the requested named factory from the registry. +func UnregisterKMS(name string) { + kmsRegMutex.Lock() + defer kmsRegMutex.Unlock() + delete(kmsRegistry, name) +} + +// GetRegisteredKMSNames returns the names of all currently registered KMS +// factories. +func GetRegisteredKMSNames() []string { + kmsRegMutex.RLock() + defer kmsRegMutex.RUnlock() + + return slices.Collect(maps.Keys(kmsRegistry)) +} + +// LoadKeyManagementClient resolves and constructs a [KeyManagementClient] +// from props. It looks up the KMS name under [KMSTypeKey] first, falling +// back to [KMSImplKey] if present; it returns an error wrapping +// [ErrKMSTypeNotFound] if neither property is set or the named KMS has not +// been registered via [RegisterKMS]. +func LoadKeyManagementClient(props map[string]string) (KeyManagementClient, error) { + name := props[KMSTypeKey] Review Comment: The doc says it falls back to KMSImplKey "if present," but the code only falls back when kms-type is empty, not when it's set to an unregistered name — so kms-type=typo + kms-impl=memory errors instead of loading memory. The behavior's defensible (an explicit type shouldn't silently swallow a typo), but the doc reads otherwise. Tighten it to "falls back only when KMSTypeKey is unset," and consider adding that set-but-unregistered-type + valid-impl case to the precedence test at kms_registry_test.go:44. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
