Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9a37f9c
add kas allowlist
elizabethhealy Apr 18, 2025
2fee0a0
trigger benchmark
elizabethhealy Apr 18, 2025
82b376d
linting
elizabethhealy Apr 18, 2025
e286508
fix tests, allow passing in decrypt options for nano and bulk
elizabethhealy Apr 18, 2025
c825b34
linting, add more tests
elizabethhealy Apr 18, 2025
8f663d5
fix bulk rewrap latency from allowlist fetching
elizabethhealy Apr 18, 2025
14e321c
add unit tests and rt test for bulk
elizabethhealy Apr 18, 2025
b50bc34
linting
elizabethhealy Apr 18, 2025
a683e93
linting
elizabethhealy Apr 18, 2025
1884486
try fix xtest branch
elizabethhealy Apr 22, 2025
00567cb
Merge branch 'main' into dspx-966-kas-allowlist
elizabethhealy Apr 22, 2025
e6c6731
back to main xtest
elizabethhealy Apr 22, 2025
6209e98
change where were doing the isallowed check
elizabethhealy Apr 23, 2025
8c8ddcb
remove redundant check
elizabethhealy Apr 23, 2025
0617c59
handle allowlist in bulk to support key splitting
elizabethhealy Apr 23, 2025
e1e6bd6
fix bulk latency
elizabethhealy Apr 23, 2025
2bd597b
apply suggestions
elizabethhealy Apr 23, 2025
b6c3202
fail if cant access kas registry
elizabethhealy Apr 23, 2025
128f222
handle schemes and platform endpoint
elizabethhealy Apr 24, 2025
7ce5d5d
fix bulk, linting
elizabethhealy Apr 24, 2025
58f6d6a
Merge branch 'main' into dspx-966-kas-allowlist
elizabethhealy Apr 24, 2025
892e077
fix encrypt example, linting
elizabethhealy Apr 24, 2025
23f7a8f
Merge branch 'dspx-966-kas-allowlist' of https://github.com/opentdf/p…
elizabethhealy Apr 24, 2025
bb6b49a
fix nano tests
elizabethhealy Apr 24, 2025
d31713b
address comments
elizabethhealy Apr 25, 2025
bdf6ed4
Merge branch 'main' into dspx-966-kas-allowlist
elizabethhealy Apr 25, 2025
bfca56a
fix nano platform endpoint
elizabethhealy Apr 25, 2025
93e9863
linting
elizabethhealy Apr 25, 2025
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
10 changes: 7 additions & 3 deletions examples/cmd/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,19 @@ func encrypt(cmd *cobra.Command, args []string) error {
}
}

baseKasUrl := platformEndpoint
if !strings.HasPrefix(baseKasUrl, "http://") && !strings.HasPrefix(baseKasUrl, "https://") {
baseKasUrl = fmt.Sprintf("http://%s", baseKasUrl)
}

if !nanoFormat {
opts := []sdk.TDFOption{sdk.WithDataAttributes(dataAttributes...)}
if !autoconfigure {
opts = append(opts, sdk.WithAutoconfigure(autoconfigure))
opts = append(opts, sdk.WithWrappingKeyAlg(ocrypto.EC256Key))
opts = append(opts, sdk.WithKasInformation(
sdk.KASInfo{
// examples assume insecure http
URL: fmt.Sprintf("http://%s", platformEndpoint),
URL: baseKasUrl,
PublicKey: "",
}))
}
Expand Down Expand Up @@ -138,7 +142,7 @@ func encrypt(cmd *cobra.Command, args []string) error {
if collection > 0 {
nanoTDFConfig.EnableCollection()
}
err = nanoTDFConfig.SetKasURL(fmt.Sprintf("http://%s/kas", platformEndpoint))
err = nanoTDFConfig.SetKasURL(fmt.Sprintf("%s/kas", baseKasUrl))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion examples/cmd/examples.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func init() {
log.SetFlags(log.LstdFlags | log.Llongfile)
f := ExamplesCmd.PersistentFlags()
f.StringVarP(&clientCredentials, "creds", "", "opentdf-sdk:secret", "client id:secret credentials")
f.StringVarP(&platformEndpoint, "platformEndpoint", "e", "localhost:8080", "Platform Endpoint")
f.StringVarP(&platformEndpoint, "platformEndpoint", "e", "http://localhost:8080", "Platform Endpoint")
f.StringVarP(&tokenEndpoint, "tokenEndpoint", "t", "http://localhost:8888/auth/realms/opentdf/protocol/openid-connect/token", "OAuth token endpoint")
f.BoolVar(&storeCollectionHeaders, "storeCollectionHeaders", false, "Store collection headers")
f.BoolVar(&insecurePlaintextConn, "insecurePlaintextConn", false, "Use insecure plaintext connection")
Expand Down
113 changes: 96 additions & 17 deletions sdk/bulk.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"

"github.com/opentdf/platform/protocol/go/kas"
)
Expand All @@ -17,8 +18,12 @@ type BulkTDF struct {
}

type BulkDecryptRequest struct {
TDFs []*BulkTDF
TDFType TdfType
TDFs []*BulkTDF
TDF3DecryptOptions []TDFReaderOption // Options for TDF3 Decryptor
NanoTDFDecryptOptions []NanoTDFReaderOption // Options for Nano TDF Decryptor
TDFType TdfType
kasAllowlist AllowList
ignoreAllowList bool
}

// BulkErrors List of Errors that Failed during Bulk Decryption
Expand All @@ -39,52 +44,111 @@ func FromBulkErrors(err error) ([]error, bool) {
return list, ok
}

type BulkDecryptOption func(request *BulkDecryptRequest)
type BulkDecryptOption func(request *BulkDecryptRequest) error

// WithTDFs Adds Lists of TDFs to be decrypted
func WithTDFs(tdfs ...*BulkTDF) BulkDecryptOption {
return func(request *BulkDecryptRequest) {
return func(request *BulkDecryptRequest) error {
request.appendTDFs(tdfs...)
return nil
}
}

// WithTDFType Type of TDFs to be decrypted
func WithTDFType(tdfType TdfType) BulkDecryptOption {
return func(request *BulkDecryptRequest) {
return func(request *BulkDecryptRequest) error {
request.TDFType = tdfType
return nil
}
}

func createBulkRewrapRequest(options ...BulkDecryptOption) *BulkDecryptRequest {
func WithBulkKasAllowlist(kasList []string) BulkDecryptOption {
return func(request *BulkDecryptRequest) error {
allowlist, err := newAllowList(kasList)
if err != nil {
return fmt.Errorf("failed to create kas allowlist: %w", err)
}
request.kasAllowlist = allowlist
return nil
}
}

func WithBulkIgnoreAllowlist(ignore bool) BulkDecryptOption {
return func(request *BulkDecryptRequest) error {
request.ignoreAllowList = ignore
return nil
}
}

func WithTDF3DecryptOptions(options ...TDFReaderOption) BulkDecryptOption {
return func(request *BulkDecryptRequest) error {
request.TDF3DecryptOptions = append(request.TDF3DecryptOptions, options...)
return nil
}
}

func WithNanoTDFDecryptOptions(options ...NanoTDFReaderOption) BulkDecryptOption {
return func(request *BulkDecryptRequest) error {
request.NanoTDFDecryptOptions = append(request.NanoTDFDecryptOptions, options...)
return nil
}
}

func createBulkRewrapRequest(options ...BulkDecryptOption) (*BulkDecryptRequest, error) {
req := &BulkDecryptRequest{}
for _, opt := range options {
opt(req)
err := opt(req)
if err != nil {
return nil, err
}
}
return req
return req, nil
}

func (s SDK) createDecryptor(tdf *BulkTDF, tdfType TdfType) (decryptor, error) {
switch tdfType {
func (s SDK) createDecryptor(tdf *BulkTDF, req *BulkDecryptRequest) (decryptor, error) {
switch req.TDFType {
case Nano:
decryptor := createNanoTDFDecryptHandler(tdf.Reader, tdf.Writer)
return decryptor, nil
return createNanoTDFDecryptHandler(tdf.Reader, tdf.Writer, req.NanoTDFDecryptOptions...)
case Standard:
return s.createTDF3DecryptHandler(tdf.Writer, tdf.Reader)
return s.createTDF3DecryptHandler(tdf.Writer, tdf.Reader, req.TDF3DecryptOptions...)
case Invalid:
}
return nil, fmt.Errorf("unknown tdf type: %s", tdfType)
return nil, fmt.Errorf("unknown tdf type: %s", req.TDFType)
}

// BulkDecrypt Decrypts a list of BulkTDF and if a partial failure of TDFs unable to be decrypted, BulkErrors would be returned.
func (s SDK) BulkDecrypt(ctx context.Context, opts ...BulkDecryptOption) error {
bulkReq := createBulkRewrapRequest(opts...)
bulkReq, createError := createBulkRewrapRequest(opts...)
if createError != nil {
return fmt.Errorf("failed to create bulk rewrap request: %w", createError)
}
kasRewrapRequests := make(map[string][]*kas.UnsignedRewrapRequest_WithPolicyRequest)
tdfDecryptors := make(map[string]decryptor)
policyTDF := make(map[string]*BulkTDF)

if !bulkReq.ignoreAllowList && len(bulkReq.kasAllowlist) == 0 { //nolint:nestif // if kasAllowlist is not set, we get it from the registry
if s.KeyAccessServerRegistry != nil {
platformEndpoint, err := s.PlatformConfiguration.platformEndpoint()
if err != nil {
return fmt.Errorf("retrieving platformEndpoint failed: %w", err)
}
// if no kasAllowlist is set, we get the allowlist from the registry
allowlist, err := allowListFromKASRegistry(ctx, s.KeyAccessServerRegistry, platformEndpoint)
if err != nil {
return fmt.Errorf("failed to get allowlist from registry: %w", err)
}
bulkReq.kasAllowlist = allowlist
bulkReq.NanoTDFDecryptOptions = append(bulkReq.NanoTDFDecryptOptions, withNanoKasAllowlist(bulkReq.kasAllowlist))
bulkReq.TDF3DecryptOptions = append(bulkReq.TDF3DecryptOptions, withKasAllowlist(bulkReq.kasAllowlist))
} else {
slog.Error("No KAS allowlist provided and no KeyAccessServerRegistry available")
return errors.New("no KAS allowlist provided and no KeyAccessServerRegistry available")
}
}

for i, tdf := range bulkReq.TDFs {
policyID := fmt.Sprintf("policy-%d", i)
decryptor, err := s.createDecryptor(tdf, bulkReq.TDFType)
decryptor, err := s.createDecryptor(tdf, bulkReq) //nolint:contextcheck // dont want to change signature of LoadTDF
if err != nil {
tdf.Error = err
continue
Expand All @@ -106,7 +170,22 @@ func (s SDK) BulkDecrypt(ctx context.Context, opts ...BulkDecryptOption) error {
kasClient := newKASClient(s.dialOptions, s.tokenSource, s.kasSessionKey)
allRewrapResp := make(map[string][]kaoResult)
var err error
for _, rewrapRequests := range kasRewrapRequests {
for kasurl, rewrapRequests := range kasRewrapRequests {
if bulkReq.ignoreAllowList {
slog.Warn(fmt.Sprintf("KasAllowlist is ignored, kas url %s is allowed", kasurl))
} else if !bulkReq.kasAllowlist.IsAllowed(kasurl) {
// if kas url is not allowed, the result for each kao in each rewrap request is set to error
for _, req := range rewrapRequests {
id := req.GetPolicy().GetId()
for _, kao := range req.GetKeyAccessObjects() {
allRewrapResp[id] = append(allRewrapResp[id], kaoResult{
Error: fmt.Errorf("KasAllowlist: kas url %s is not allowed", kasurl),
KeyAccessObjectID: kao.GetKeyAccessObjectId(),
})
}
}
continue
}
var rewrapResp map[string][]kaoResult
switch bulkReq.TDFType {
case Nano:
Expand Down
42 changes: 36 additions & 6 deletions sdk/nanotdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -907,13 +907,20 @@ type NanoTDFDecryptHandler struct {

header NanoTDFHeader
headerBuf []byte

config *NanoTDFReaderConfig
}

func createNanoTDFDecryptHandler(reader io.ReadSeeker, writer io.Writer) *NanoTDFDecryptHandler {
func createNanoTDFDecryptHandler(reader io.ReadSeeker, writer io.Writer, opts ...NanoTDFReaderOption) (*NanoTDFDecryptHandler, error) {
nanoTdfReaderConfig, err := newNanoTDFReaderConfig(opts...)
if err != nil {
return nil, fmt.Errorf("newNanoTDFReaderConfig failed: %w", err)
}
return &NanoTDFDecryptHandler{
reader: reader,
writer: writer,
}
config: nanoTdfReaderConfig,
}, nil
}

func (n *NanoTDFDecryptHandler) getRawHeader() []byte {
Expand Down Expand Up @@ -942,6 +949,12 @@ func (n *NanoTDFDecryptHandler) CreateRewrapRequest(_ context.Context) (map[stri
return nil, err
}

if n.config.ignoreAllowList {
slog.Warn(fmt.Sprintf("KasAllowlist is ignored, kas url %s is allowed", kasURL))
} else if !n.config.kasAllowlist.IsAllowed(kasURL) {
return nil, fmt.Errorf("KasAllowlist: kas url %s is not allowed", kasURL)
}

req := &kas.UnsignedRewrapRequest_WithPolicyRequest{
KeyAccessObjects: []*kas.UnsignedRewrapRequest_WithKeyAccessObject{
{
Expand Down Expand Up @@ -1017,13 +1030,30 @@ func (n *NanoTDFDecryptHandler) Decrypt(_ context.Context, result []kaoResult) (
}

// ReadNanoTDF - read the nano tdf and return the decrypted data from it
func (s SDK) ReadNanoTDF(writer io.Writer, reader io.ReadSeeker) (int, error) {
return s.ReadNanoTDFContext(context.Background(), writer, reader)
func (s SDK) ReadNanoTDF(writer io.Writer, reader io.ReadSeeker, opts ...NanoTDFReaderOption) (int, error) {
return s.ReadNanoTDFContext(context.Background(), writer, reader, opts...)
}

// ReadNanoTDFContext - allows cancelling the reader
func (s SDK) ReadNanoTDFContext(ctx context.Context, writer io.Writer, reader io.ReadSeeker) (int, error) {
handler := createNanoTDFDecryptHandler(reader, writer)
func (s SDK) ReadNanoTDFContext(ctx context.Context, writer io.Writer, reader io.ReadSeeker, opts ...NanoTDFReaderOption) (int, error) {
handler, err := createNanoTDFDecryptHandler(reader, writer, opts...)
if err != nil {
return 0, fmt.Errorf("createNanoTDFDecryptHandler failed: %w", err)
}

if len(handler.config.kasAllowlist) == 0 && !handler.config.ignoreAllowList {
if s.KeyAccessServerRegistry != nil {
// retrieve the registered kases if not provided
allowList, err := allowListFromKASRegistry(ctx, s.KeyAccessServerRegistry, s.conn.Target())
if err != nil {
return 0, fmt.Errorf("allowListFromKASRegistry failed: %w", err)
}
handler.config.kasAllowlist = allowList
} else {
slog.Error("No KAS allowlist provided and no KeyAccessServerRegistry available")
return 0, errors.New("no KAS allowlist provided and no KeyAccessServerRegistry available")
}
}

symmetricKey, err := s.getNanoRewrapKey(ctx, handler)
if err != nil {
Expand Down
45 changes: 45 additions & 0 deletions sdk/nanotdf_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,48 @@ func WithECDSAPolicyBinding() NanoTDFOption {
return nil
}
}

type NanoTDFReaderConfig struct {
kasAllowlist AllowList
ignoreAllowList bool
}

func newNanoTDFReaderConfig(opt ...NanoTDFReaderOption) (*NanoTDFReaderConfig, error) {
c := &NanoTDFReaderConfig{}

for _, o := range opt {
err := o(c)
if err != nil {
return nil, err
}
}

return c, nil
}

type NanoTDFReaderOption func(*NanoTDFReaderConfig) error

func WithNanoKasAllowlist(kasList []string) NanoTDFReaderOption {
return func(c *NanoTDFReaderConfig) error {
allowlist, err := newAllowList(kasList)
if err != nil {
return fmt.Errorf("failed to create kas allowlist: %w", err)
}
c.kasAllowlist = allowlist
return nil
}
}

func withNanoKasAllowlist(allowlist AllowList) NanoTDFReaderOption {
return func(c *NanoTDFReaderConfig) error {
c.kasAllowlist = allowlist
return nil
}
}

func WithNanoIgnoreAllowlist(ignore bool) NanoTDFReaderOption {
return func(c *NanoTDFReaderConfig) error {
c.ignoreAllowList = ignore
return nil
}
}
Loading
Loading