From a12c413b84c4657bfb7aa14948b32cbe68d56932 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Wed, 24 Mar 2021 16:45:46 -0400 Subject: [PATCH 1/4] plans/planfile: Add required-replace and sensitive The stored planfile now serializes the required-replace path set and the collection of before/after sensitivity marks. This ensures that storing a plan and displaying it with `terraform show` renders the same output for plans with required-replace resources, and those with sensitive values in the diff. --- plans/changes_src.go | 3 - plans/internal/planproto/planfile.pb.go | 203 ++++++++++++++---------- plans/internal/planproto/planfile.proto | 10 ++ plans/planfile/tfplan.go | 130 +++++++++++++++ plans/planfile/tfplan_test.go | 16 ++ 5 files changed, 271 insertions(+), 91 deletions(-) diff --git a/plans/changes_src.go b/plans/changes_src.go index 683c9f174..055f222db 100644 --- a/plans/changes_src.go +++ b/plans/changes_src.go @@ -37,9 +37,6 @@ type ResourceInstanceChangeSrc struct { // RequiredReplace is a set of paths that caused the change action to be // Replace rather than Update. Always nil if the change action is not // Replace. - // - // This is retained only for UI-plan-rendering purposes and so it does not - // currently survive a round-trip through a saved plan file. RequiredReplace cty.PathSet // Private allows a provider to stash any extra data that is opaque to diff --git a/plans/internal/planproto/planfile.pb.go b/plans/internal/planproto/planfile.pb.go index 9fc4daf59..1631da460 100644 --- a/plans/internal/planproto/planfile.pb.go +++ b/plans/internal/planproto/planfile.pb.go @@ -1,13 +1,12 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0 -// protoc v3.14.0 +// protoc-gen-go v1.26.0-devel +// protoc v3.15.6 // source: planfile.proto package planproto import ( - proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -21,10 +20,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - // Action describes the type of action planned for an object. // Not all action values are valid for all object types. type Action int32 @@ -347,6 +342,14 @@ type Change struct { // respectively. // - For no-op, one value is provided that is left unmodified by this non-change. Values []*DynamicValue `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"` + // An unordered set of paths into the old value which are marked as + // sensitive. Values at these paths should be obscured in human-readable + // output. This set is always empty for create. + BeforeSensitivePaths []*Path `protobuf:"bytes,3,rep,name=before_sensitive_paths,json=beforeSensitivePaths,proto3" json:"before_sensitive_paths,omitempty"` + // An unordered set of paths into the new value which are marked as + // sensitive. Values at these paths should be obscured in human-readable + // output. This set is always empty for delete. + AfterSensitivePaths []*Path `protobuf:"bytes,4,rep,name=after_sensitive_paths,json=afterSensitivePaths,proto3" json:"after_sensitive_paths,omitempty"` } func (x *Change) Reset() { @@ -395,6 +398,20 @@ func (x *Change) GetValues() []*DynamicValue { return nil } +func (x *Change) GetBeforeSensitivePaths() []*Path { + if x != nil { + return x.BeforeSensitivePaths + } + return nil +} + +func (x *Change) GetAfterSensitivePaths() []*Path { + if x != nil { + return x.AfterSensitivePaths + } + return nil +} + type ResourceInstanceChange struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -941,74 +958,82 @@ var file_planfile_proto_rawDesc = []byte{ 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x22, 0x5e, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x26, - 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, - 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, - 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x22, 0xb9, 0x03, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, - 0x12, 0x3f, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, - 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x73, 0x74, 0x72, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x73, 0x74, 0x72, 0x12, 0x12, 0x0a, - 0x03, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x03, 0x69, 0x6e, - 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x4b, - 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x26, - 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, - 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, - 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, - 0x12, 0x37, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x70, - 0x6c, 0x61, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, - 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x22, 0x25, 0x0a, 0x0c, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x64, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x10, 0x01, - 0x42, 0x0e, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x22, 0x68, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, - 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79, - 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, - 0x67, 0x70, 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67, - 0x70, 0x61, 0x63, 0x6b, 0x22, 0x1e, 0x0a, 0x04, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x68, - 0x61, 0x32, 0x35, 0x36, 0x22, 0xa5, 0x01, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x27, 0x0a, - 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, - 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52, - 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x74, 0x0a, 0x04, 0x53, 0x74, 0x65, 0x70, 0x12, 0x27, - 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x65, 0x6c, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, - 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, - 0x42, 0x0a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2a, 0x70, 0x0a, 0x06, - 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, - 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, - 0x52, 0x45, 0x41, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, - 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x05, 0x12, 0x16, - 0x0a, 0x12, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x43, 0x52, - 0x45, 0x41, 0x54, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, - 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x07, 0x42, 0x39, - 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, - 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, - 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, - 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x70, 0x61, 0x63, 0x65, 0x22, 0xe4, 0x01, 0x0a, 0x06, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, + 0x26, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, + 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x16, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, + 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, + 0x61, 0x74, 0x68, 0x52, 0x14, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x6e, 0x73, 0x69, + 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x40, 0x0a, 0x15, 0x61, 0x66, 0x74, + 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, + 0x68, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, + 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x13, 0x61, 0x66, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, + 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x22, 0xb9, 0x03, 0x0a, 0x16, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, + 0x75, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x3f, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, + 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x12, 0x0a, 0x03, 0x73, 0x74, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x03, 0x73, 0x74, 0x72, 0x12, 0x12, 0x0a, 0x03, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x48, 0x00, 0x52, 0x03, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, + 0x65, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x37, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, + 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, + 0x22, 0x25, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x6f, 0x64, 0x65, + 0x12, 0x0b, 0x0a, 0x07, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x10, 0x00, 0x12, 0x08, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x10, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x68, 0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x74, 0x66, + 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, + 0x65, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6b, 0x22, 0x1e, 0x0a, 0x04, 0x48, + 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x22, 0xa5, 0x01, 0x0a, 0x04, + 0x50, 0x61, 0x74, 0x68, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x61, 0x74, + 0x68, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x1a, 0x74, 0x0a, + 0x04, 0x53, 0x74, 0x65, 0x70, 0x12, 0x27, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x0d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, + 0x0a, 0x0b, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, + 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x65, 0x6c, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x2a, 0x70, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, + 0x04, 0x4e, 0x4f, 0x4f, 0x50, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, + 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x52, 0x45, 0x41, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, + 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, + 0x45, 0x54, 0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x5f, + 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, + 0x12, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4e, 0x5f, 0x44, 0x45, 0x4c, + 0x45, 0x54, 0x45, 0x10, 0x07, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x74, 0x65, + 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x2f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1049,19 +1074,21 @@ var file_planfile_proto_depIdxs = []int32{ 7, // 5: tfplan.Backend.config:type_name -> tfplan.DynamicValue 0, // 6: tfplan.Change.action:type_name -> tfplan.Action 7, // 7: tfplan.Change.values:type_name -> tfplan.DynamicValue - 1, // 8: tfplan.ResourceInstanceChange.mode:type_name -> tfplan.ResourceInstanceChange.ResourceMode - 4, // 9: tfplan.ResourceInstanceChange.change:type_name -> tfplan.Change - 9, // 10: tfplan.ResourceInstanceChange.required_replace:type_name -> tfplan.Path - 4, // 11: tfplan.OutputChange.change:type_name -> tfplan.Change - 12, // 12: tfplan.Path.steps:type_name -> tfplan.Path.Step - 7, // 13: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue - 8, // 14: tfplan.Plan.ProviderHashesEntry.value:type_name -> tfplan.Hash - 7, // 15: tfplan.Path.Step.element_key:type_name -> tfplan.DynamicValue - 16, // [16:16] is the sub-list for method output_type - 16, // [16:16] is the sub-list for method input_type - 16, // [16:16] is the sub-list for extension type_name - 16, // [16:16] is the sub-list for extension extendee - 0, // [0:16] is the sub-list for field type_name + 9, // 8: tfplan.Change.before_sensitive_paths:type_name -> tfplan.Path + 9, // 9: tfplan.Change.after_sensitive_paths:type_name -> tfplan.Path + 1, // 10: tfplan.ResourceInstanceChange.mode:type_name -> tfplan.ResourceInstanceChange.ResourceMode + 4, // 11: tfplan.ResourceInstanceChange.change:type_name -> tfplan.Change + 9, // 12: tfplan.ResourceInstanceChange.required_replace:type_name -> tfplan.Path + 4, // 13: tfplan.OutputChange.change:type_name -> tfplan.Change + 12, // 14: tfplan.Path.steps:type_name -> tfplan.Path.Step + 7, // 15: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue + 8, // 16: tfplan.Plan.ProviderHashesEntry.value:type_name -> tfplan.Hash + 7, // 17: tfplan.Path.Step.element_key:type_name -> tfplan.DynamicValue + 18, // [18:18] is the sub-list for method output_type + 18, // [18:18] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_planfile_proto_init() } diff --git a/plans/internal/planproto/planfile.proto b/plans/internal/planproto/planfile.proto index f45c3005a..fe5ef8e43 100644 --- a/plans/internal/planproto/planfile.proto +++ b/plans/internal/planproto/planfile.proto @@ -85,6 +85,16 @@ message Change { // respectively. // - For no-op, one value is provided that is left unmodified by this non-change. repeated DynamicValue values = 2; + + // An unordered set of paths into the old value which are marked as + // sensitive. Values at these paths should be obscured in human-readable + // output. This set is always empty for create. + repeated Path before_sensitive_paths = 3; + + // An unordered set of paths into the new value which are marked as + // sensitive. Values at these paths should be obscured in human-readable + // output. This set is always empty for delete. + repeated Path after_sensitive_paths = 4; } message ResourceInstanceChange { diff --git a/plans/planfile/tfplan.go b/plans/planfile/tfplan.go index 5162aae75..1c47de5f5 100644 --- a/plans/planfile/tfplan.go +++ b/plans/planfile/tfplan.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/version" + "github.com/zclconf/go-cty/cty" ) const tfplanFormatVersion = 3 @@ -190,6 +191,15 @@ func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*pla ret.DeposedKey = states.DeposedKey(rawChange.DeposedKey) } + ret.RequiredReplace = cty.NewPathSet() + for _, p := range rawChange.RequiredReplace { + path, err := pathFromTfplan(p) + if err != nil { + return nil, fmt.Errorf("invalid path in required replace: %s", err) + } + ret.RequiredReplace.Add(path) + } + change, err := changeFromTfplan(rawChange.Change) if err != nil { return nil, fmt.Errorf("invalid plan for resource %s: %s", ret.Addr, err) @@ -273,6 +283,22 @@ func changeFromTfplan(rawChange *planproto.Change) (*plans.ChangeSrc, error) { } } + sensitive := cty.NewValueMarks("sensitive") + beforeValMarks, err := pathValueMarksFromTfplan(rawChange.BeforeSensitivePaths, sensitive) + if err != nil { + return nil, fmt.Errorf("failed to decode before sensitive paths: %s", err) + } + afterValMarks, err := pathValueMarksFromTfplan(rawChange.AfterSensitivePaths, sensitive) + if err != nil { + return nil, fmt.Errorf("failed to decode after sensitive paths: %s", err) + } + if len(beforeValMarks) > 0 { + ret.BeforeValMarks = beforeValMarks + } + if len(afterValMarks) > 0 { + ret.AfterValMarks = afterValMarks + } + return ret, nil } @@ -414,6 +440,16 @@ func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto ret.DeposedKey = string(change.DeposedKey) ret.Provider = change.ProviderAddr.String() + requiredReplace := change.RequiredReplace.List() + ret.RequiredReplace = make([]*planproto.Path, 0, len(requiredReplace)) + for _, p := range requiredReplace { + path, err := pathToTfplan(p) + if err != nil { + return nil, fmt.Errorf("invalid path in required replace: %s", err) + } + ret.RequiredReplace = append(ret.RequiredReplace, path) + } + valChange, err := changeToTfplan(&change.ChangeSrc) if err != nil { return nil, fmt.Errorf("failed to serialize resource %s change: %s", relAddr, err) @@ -433,6 +469,17 @@ func changeToTfplan(change *plans.ChangeSrc) (*planproto.Change, error) { before := valueToTfplan(change.Before) after := valueToTfplan(change.After) + beforeSensitivePaths, err := pathValueMarksToTfplan(change.BeforeValMarks) + if err != nil { + return nil, err + } + afterSensitivePaths, err := pathValueMarksToTfplan(change.AfterValMarks) + if err != nil { + return nil, err + } + ret.BeforeSensitivePaths = beforeSensitivePaths + ret.AfterSensitivePaths = afterSensitivePaths + switch change.Action { case plans.NoOp: ret.Action = planproto.Action_NOOP @@ -472,3 +519,86 @@ func valueToTfplan(val plans.DynamicValue) *planproto.DynamicValue { Msgpack: []byte(val), } } + +func pathValueMarksFromTfplan(paths []*planproto.Path, marks cty.ValueMarks) ([]cty.PathValueMarks, error) { + ret := make([]cty.PathValueMarks, 0, len(paths)) + for _, p := range paths { + path, err := pathFromTfplan(p) + if err != nil { + return nil, err + } + ret = append(ret, cty.PathValueMarks{ + Path: path, + Marks: marks, + }) + } + return ret, nil +} + +func pathValueMarksToTfplan(pvm []cty.PathValueMarks) ([]*planproto.Path, error) { + ret := make([]*planproto.Path, 0, len(pvm)) + for _, p := range pvm { + path, err := pathToTfplan(p.Path) + if err != nil { + return nil, err + } + ret = append(ret, path) + } + return ret, nil +} + +func pathFromTfplan(path *planproto.Path) (cty.Path, error) { + ret := make([]cty.PathStep, 0, len(path.Steps)) + for _, step := range path.Steps { + switch s := step.Selector.(type) { + case *planproto.Path_Step_ElementKey: + dynamicVal, err := valueFromTfplan(s.ElementKey) + if err != nil { + return nil, fmt.Errorf("error decoding path index step: %s", err) + } + ty, err := dynamicVal.ImpliedType() + if err != nil { + return nil, fmt.Errorf("error determining path index type: %s", err) + } + val, err := dynamicVal.Decode(ty) + if err != nil { + return nil, fmt.Errorf("error decoding path index value: %s", err) + } + ret = append(ret, cty.IndexStep{Key: val}) + case *planproto.Path_Step_AttributeName: + ret = append(ret, cty.GetAttrStep{Name: s.AttributeName}) + default: + return nil, fmt.Errorf("Unsupported path step %t", step.Selector) + } + } + return ret, nil +} + +func pathToTfplan(path cty.Path) (*planproto.Path, error) { + steps := make([]*planproto.Path_Step, 0, len(path)) + for _, step := range path { + switch s := step.(type) { + case cty.IndexStep: + value, err := plans.NewDynamicValue(s.Key, s.Key.Type()) + if err != nil { + return nil, fmt.Errorf("Error encoding path step: %s", err) + } + steps = append(steps, &planproto.Path_Step{ + Selector: &planproto.Path_Step_ElementKey{ + ElementKey: valueToTfplan(value), + }, + }) + case cty.GetAttrStep: + steps = append(steps, &planproto.Path_Step{ + Selector: &planproto.Path_Step_AttributeName{ + AttributeName: s.Name, + }, + }) + default: + return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step) + } + } + return &planproto.Path{ + Steps: steps, + }, nil +} diff --git a/plans/planfile/tfplan_test.go b/plans/planfile/tfplan_test.go index ffd6d1c49..f396338ef 100644 --- a/plans/planfile/tfplan_test.go +++ b/plans/planfile/tfplan_test.go @@ -64,11 +64,27 @@ func TestTFPlanRoundTrip(t *testing.T) { Action: plans.DeleteThenCreate, Before: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("foo-bar-baz"), + "boop": cty.ListVal([]cty.Value{ + cty.StringVal("beep"), + }), }), objTy), After: mustNewDynamicValue(cty.ObjectVal(map[string]cty.Value{ "id": cty.UnknownVal(cty.String), + "boop": cty.ListVal([]cty.Value{ + cty.StringVal("beep"), + cty.StringVal("honk"), + }), }), objTy), + AfterValMarks: []cty.PathValueMarks{ + { + Path: cty.GetAttrPath("boop").IndexInt(1), + Marks: cty.NewValueMarks("sensitive"), + }, + }, }, + RequiredReplace: cty.NewPathSet( + cty.GetAttrPath("boop"), + ), }, { Addr: addrs.Resource{ From e27aacebf938f8e84f9d0d0ffcc0dfe826afb6bc Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Thu, 25 Mar 2021 11:41:49 -0400 Subject: [PATCH 2/4] command/jsonplan: Add sensitive value mapping data Similar to `after_unknown`, `before_sensitive` and `after_sensitive` are values with similar structure to `before` and `after` which encode the presence of sensitive values in a planned change. These should be used to obscure sensitive values from human-readable output. These values follow the same structure as the `before` and `after` values, replacing sensitive values with `true`, and non-sensitive values with `false`. Following the `after_unknown` precedent, we omit non-sensitive `false` values for object attributes/map values, to make serialization more compact. One difference from `after_unknown` is that a sensitive complex value (collection or structural type) is replaced with `true`. If the complex value itself is sensitive, all of its contents should be obscured. --- command/jsonplan/plan.go | 118 ++++++++++- command/jsonplan/plan_test.go | 197 ++++++++++++++++++ .../show-json/basic-create/output.json | 12 +- .../show-json/basic-delete/output.json | 8 +- .../show-json/basic-update/output.json | 4 +- .../show-json/module-depends-on/output.json | 4 +- .../testdata/show-json/modules/output.json | 16 +- .../multi-resource-update/output.json | 8 +- .../show-json/nested-modules/output.json | 4 +- .../provider-version-no-config/output.json | 12 +- .../show-json/provider-version/output.json | 12 +- .../show-json/sensitive-values/main.tf | 13 ++ .../show-json/sensitive-values/output.json | 114 ++++++++++ 13 files changed, 495 insertions(+), 27 deletions(-) create mode 100644 command/testdata/show-json/sensitive-values/main.tf create mode 100644 command/testdata/show-json/sensitive-values/output.json diff --git a/command/jsonplan/plan.go b/command/jsonplan/plan.go index ee12a408c..07d7f46b9 100644 --- a/command/jsonplan/plan.go +++ b/command/jsonplan/plan.go @@ -67,9 +67,23 @@ type change struct { // or "after" is unset (respectively). For ["no-op"], the before and after // values are identical. The "after" value will be incomplete if there are // values within it that won't be known until after apply. - Before json.RawMessage `json:"before,omitempty"` - After json.RawMessage `json:"after,omitempty"` + Before json.RawMessage `json:"before,omitempty"` + After json.RawMessage `json:"after,omitempty"` + + // AfterUnknown is an object value with similar structure to After, but + // with all unknown leaf values replaced with true, and all known leaf + // values omitted. This can be combined with After to reconstruct a full + // value after the action, including values which will only be known after + // apply. AfterUnknown json.RawMessage `json:"after_unknown,omitempty"` + + // BeforeSensitive and AfterSensitive are object values with similar + // structure to Before and After, but with all sensitive leaf values + // replaced with true, and all non-sensitive leaf values omitted. These + // objects should be combined with Before and After to prevent accidental + // display of sensitive values in user interfaces. + BeforeSensitive json.RawMessage `json:"before_sensitive,omitempty"` + AfterSensitive json.RawMessage `json:"after_sensitive,omitempty"` } type output struct { @@ -192,12 +206,18 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform } var before, after []byte + var beforeSensitive, afterSensitive []byte var afterUnknown cty.Value if changeV.Before != cty.NilVal { before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type()) if err != nil { return err } + bs := sensitiveAsBool(changeV.Before.MarkWithPaths(rc.BeforeValMarks)) + beforeSensitive, err = ctyjson.Marshal(bs, bs.Type()) + if err != nil { + return err + } } if changeV.After != cty.NilVal { if changeV.After.IsWhollyKnown() { @@ -218,6 +238,11 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform } afterUnknown = unknownAsBool(changeV.After) } + as := sensitiveAsBool(changeV.After.MarkWithPaths(rc.AfterValMarks)) + afterSensitive, err = ctyjson.Marshal(as, as.Type()) + if err != nil { + return err + } } a, err := ctyjson.Marshal(afterUnknown, afterUnknown.Type()) @@ -226,10 +251,12 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform } r.Change = change{ - Actions: actionString(rc.Action.String()), - Before: json.RawMessage(before), - After: json.RawMessage(after), - AfterUnknown: a, + Actions: actionString(rc.Action.String()), + Before: json.RawMessage(before), + After: json.RawMessage(after), + AfterUnknown: a, + BeforeSensitive: json.RawMessage(beforeSensitive), + AfterSensitive: json.RawMessage(afterSensitive), } if rc.DeposedKey != states.NotDeposed { @@ -392,6 +419,9 @@ func omitUnknowns(val cty.Value) cty.Value { // tuple types and all mapping types are converted to object types, since we // assume the result of this is just going to be serialized as JSON (and thus // lose those distinctions) anyway. +// +// For map/object values, all known attribute values will be omitted instead of +// returning false, as this results in a more compact serialization. func unknownAsBool(val cty.Value) cty.Value { ty := val.Type() switch { @@ -439,7 +469,9 @@ func unknownAsBool(val cty.Value) cty.Value { for it.Next() { k, v := it.Element() vAsBool := unknownAsBool(v) - if !vAsBool.RawEquals(cty.False) { // all of the "false"s for known values for more compact serialization + // Omit all of the "false"s for known values for more compact + // serialization + if !vAsBool.RawEquals(cty.False) { vals[k.AsString()] = unknownAsBool(v) } } @@ -455,6 +487,78 @@ func unknownAsBool(val cty.Value) cty.Value { } } +// recursively iterate through a marked cty.Value, replacing sensitive values +// with cty.True and non-sensitive values with cty.False. +// +// The result also normalizes some types: all sequence types are turned into +// tuple types and all mapping types are converted to object types, since we +// assume the result of this is just going to be serialized as JSON (and thus +// lose those distinctions) anyway. +// +// For map/object values, all non-sensitive attribute values will be omitted +// instead of returning false, as this results in a more compact serialization. +func sensitiveAsBool(val cty.Value) cty.Value { + if val.HasMark("sensitive") { + return cty.True + } + + ty := val.Type() + switch { + case val.IsNull(), ty.IsPrimitiveType(), ty.Equals(cty.DynamicPseudoType): + return cty.False + case ty.IsListType() || ty.IsTupleType() || ty.IsSetType(): + length := val.LengthInt() + if length == 0 { + // If there are no elements then we can't have sensitive values + return cty.EmptyTupleVal + } + vals := make([]cty.Value, 0, length) + it := val.ElementIterator() + for it.Next() { + _, v := it.Element() + vals = append(vals, sensitiveAsBool(v)) + } + // The above transform may have changed the types of some of the + // elements, so we'll always use a tuple here in case we've now made + // different elements have different types. Our ultimate goal is to + // marshal to JSON anyway, and all of these sequence types are + // indistinguishable in JSON. + return cty.TupleVal(vals) + case ty.IsMapType() || ty.IsObjectType(): + var length int + switch { + case ty.IsMapType(): + length = val.LengthInt() + default: + length = len(val.Type().AttributeTypes()) + } + if length == 0 { + // If there are no elements then we can't have sensitive values + return cty.EmptyObjectVal + } + vals := make(map[string]cty.Value) + it := val.ElementIterator() + for it.Next() { + k, v := it.Element() + s := sensitiveAsBool(v) + // Omit all of the "false"s for non-sensitive values for more + // compact serialization + if !s.RawEquals(cty.False) { + vals[k.AsString()] = s + } + } + // The above transform may have changed the types of some of the + // elements, so we'll always use an object here in case we've now made + // different elements have different types. Our ultimate goal is to + // marshal to JSON anyway, and all of these mapping types are + // indistinguishable in JSON. + return cty.ObjectVal(vals) + default: + // Should never happen, since the above should cover all types + panic(fmt.Sprintf("sensitiveAsBool cannot handle %#v", val)) + } +} + func actionString(action string) []string { switch { case action == "NoOp": diff --git a/command/jsonplan/plan_test.go b/command/jsonplan/plan_test.go index 2a2e6b146..f6ee837fe 100644 --- a/command/jsonplan/plan_test.go +++ b/command/jsonplan/plan_test.go @@ -262,3 +262,200 @@ func TestUnknownAsBool(t *testing.T) { } } } + +func TestSensitiveAsBool(t *testing.T) { + sensitive := "sensitive" + tests := []struct { + Input cty.Value + Want cty.Value + }{ + { + cty.StringVal("hello"), + cty.False, + }, + { + cty.NullVal(cty.String), + cty.False, + }, + { + cty.StringVal("hello").Mark(sensitive), + cty.True, + }, + { + cty.NullVal(cty.String).Mark(sensitive), + cty.True, + }, + + { + cty.NullVal(cty.DynamicPseudoType).Mark(sensitive), + cty.True, + }, + { + cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})), + cty.False, + }, + { + cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})).Mark(sensitive), + cty.True, + }, + { + cty.DynamicVal, + cty.False, + }, + { + cty.DynamicVal.Mark(sensitive), + cty.True, + }, + + { + cty.ListValEmpty(cty.String), + cty.EmptyTupleVal, + }, + { + cty.ListValEmpty(cty.String).Mark(sensitive), + cty.True, + }, + { + cty.ListVal([]cty.Value{ + cty.StringVal("hello"), + cty.StringVal("friend").Mark(sensitive), + }), + cty.TupleVal([]cty.Value{ + cty.False, + cty.True, + }), + }, + { + cty.SetValEmpty(cty.String), + cty.EmptyTupleVal, + }, + { + cty.SetValEmpty(cty.String).Mark(sensitive), + cty.True, + }, + { + cty.SetVal([]cty.Value{cty.StringVal("hello")}), + cty.TupleVal([]cty.Value{cty.False}), + }, + { + cty.SetVal([]cty.Value{cty.StringVal("hello").Mark(sensitive)}), + cty.True, + }, + { + cty.EmptyTupleVal.Mark(sensitive), + cty.True, + }, + { + cty.TupleVal([]cty.Value{ + cty.StringVal("hello"), + cty.StringVal("friend").Mark(sensitive), + }), + cty.TupleVal([]cty.Value{ + cty.False, + cty.True, + }), + }, + { + cty.MapValEmpty(cty.String), + cty.EmptyObjectVal, + }, + { + cty.MapValEmpty(cty.String).Mark(sensitive), + cty.True, + }, + { + cty.MapVal(map[string]cty.Value{ + "greeting": cty.StringVal("hello"), + "animal": cty.StringVal("horse"), + }), + cty.EmptyObjectVal, + }, + { + cty.MapVal(map[string]cty.Value{ + "greeting": cty.StringVal("hello"), + "animal": cty.StringVal("horse").Mark(sensitive), + }), + cty.ObjectVal(map[string]cty.Value{ + "animal": cty.True, + }), + }, + { + cty.MapVal(map[string]cty.Value{ + "greeting": cty.StringVal("hello"), + "animal": cty.StringVal("horse").Mark(sensitive), + }).Mark(sensitive), + cty.True, + }, + { + cty.EmptyObjectVal, + cty.EmptyObjectVal, + }, + { + cty.ObjectVal(map[string]cty.Value{ + "greeting": cty.StringVal("hello"), + "animal": cty.StringVal("horse"), + }), + cty.EmptyObjectVal, + }, + { + cty.ObjectVal(map[string]cty.Value{ + "greeting": cty.StringVal("hello"), + "animal": cty.StringVal("horse").Mark(sensitive), + }), + cty.ObjectVal(map[string]cty.Value{ + "animal": cty.True, + }), + }, + { + cty.ObjectVal(map[string]cty.Value{ + "greeting": cty.StringVal("hello"), + "animal": cty.StringVal("horse").Mark(sensitive), + }).Mark(sensitive), + cty.True, + }, + { + cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "a": cty.UnknownVal(cty.String), + }), + cty.ObjectVal(map[string]cty.Value{ + "a": cty.StringVal("known").Mark(sensitive), + }), + }), + cty.TupleVal([]cty.Value{ + cty.EmptyObjectVal, + cty.ObjectVal(map[string]cty.Value{ + "a": cty.True, + }), + }), + }, + { + cty.ListVal([]cty.Value{ + cty.MapValEmpty(cty.String), + cty.MapVal(map[string]cty.Value{ + "a": cty.StringVal("known").Mark(sensitive), + }), + cty.MapVal(map[string]cty.Value{ + "a": cty.UnknownVal(cty.String), + }), + }), + cty.TupleVal([]cty.Value{ + cty.EmptyObjectVal, + cty.ObjectVal(map[string]cty.Value{ + "a": cty.True, + }), + cty.EmptyObjectVal, + }), + }, + } + + for _, test := range tests { + got := sensitiveAsBool(test.Input) + if !reflect.DeepEqual(got, test.Want) { + t.Errorf( + "wrong result\ninput: %#v\ngot: %#v\nwant: %#v", + test.Input, got, test.Want, + ) + } + } +} diff --git a/command/testdata/show-json/basic-create/output.json b/command/testdata/show-json/basic-create/output.json index ff83de3fe..01a26d09b 100644 --- a/command/testdata/show-json/basic-create/output.json +++ b/command/testdata/show-json/basic-create/output.json @@ -83,7 +83,9 @@ }, "after": { "ami": "bar" - } + }, + "after_sensitive": {}, + "before_sensitive": false } }, { @@ -103,7 +105,9 @@ }, "after": { "ami": "bar" - } + }, + "after_sensitive": {}, + "before_sensitive": false } }, { @@ -123,7 +127,9 @@ }, "after": { "ami": "bar" - } + }, + "after_sensitive": {}, + "before_sensitive": false } } ], diff --git a/command/testdata/show-json/basic-delete/output.json b/command/testdata/show-json/basic-delete/output.json index f9580b401..f9efd426f 100644 --- a/command/testdata/show-json/basic-delete/output.json +++ b/command/testdata/show-json/basic-delete/output.json @@ -48,7 +48,9 @@ "ami": "bar", "id": "placeholder" }, - "after_unknown": {} + "after_unknown": {}, + "after_sensitive": {}, + "before_sensitive": {} } }, { @@ -66,7 +68,9 @@ "id": "placeholder" }, "after": null, - "after_unknown": {} + "after_unknown": {}, + "after_sensitive": false, + "before_sensitive": {} } } ], diff --git a/command/testdata/show-json/basic-update/output.json b/command/testdata/show-json/basic-update/output.json index 5486bece2..8a2f4de6f 100644 --- a/command/testdata/show-json/basic-update/output.json +++ b/command/testdata/show-json/basic-update/output.json @@ -48,7 +48,9 @@ "ami": "bar", "id": "placeholder" }, - "after_unknown": {} + "after_unknown": {}, + "after_sensitive": {}, + "before_sensitive": {} } } ], diff --git a/command/testdata/show-json/module-depends-on/output.json b/command/testdata/show-json/module-depends-on/output.json index 26e7e218b..5bfb89694 100644 --- a/command/testdata/show-json/module-depends-on/output.json +++ b/command/testdata/show-json/module-depends-on/output.json @@ -35,7 +35,9 @@ }, "after_unknown": { "id": true - } + }, + "after_sensitive": {}, + "before_sensitive": false } } ], diff --git a/command/testdata/show-json/modules/output.json b/command/testdata/show-json/modules/output.json index 898763aad..b78a9d1ab 100644 --- a/command/testdata/show-json/modules/output.json +++ b/command/testdata/show-json/modules/output.json @@ -99,7 +99,9 @@ }, "after_unknown": { "id": true - } + }, + "after_sensitive": {}, + "before_sensitive": false } }, { @@ -120,7 +122,9 @@ }, "after_unknown": { "id": true - } + }, + "after_sensitive": {}, + "before_sensitive": false } }, { @@ -141,7 +145,9 @@ }, "after_unknown": { "id": true - } + }, + "after_sensitive": {}, + "before_sensitive": false } }, { @@ -162,7 +168,9 @@ }, "after_unknown": { "id": true - } + }, + "after_sensitive": {}, + "before_sensitive": false } } ], diff --git a/command/testdata/show-json/multi-resource-update/output.json b/command/testdata/show-json/multi-resource-update/output.json index 6725c2546..a0418499f 100644 --- a/command/testdata/show-json/multi-resource-update/output.json +++ b/command/testdata/show-json/multi-resource-update/output.json @@ -63,7 +63,9 @@ "ami": "bar", "id": "placeholder" }, - "after_unknown": {} + "after_unknown": {}, + "after_sensitive": {}, + "before_sensitive": {} } }, { @@ -83,7 +85,9 @@ }, "after_unknown": { "id": true - } + }, + "after_sensitive": {}, + "before_sensitive": false } } ], diff --git a/command/testdata/show-json/nested-modules/output.json b/command/testdata/show-json/nested-modules/output.json index 369a58a4d..96e6b39e9 100644 --- a/command/testdata/show-json/nested-modules/output.json +++ b/command/testdata/show-json/nested-modules/output.json @@ -45,7 +45,9 @@ }, "after_unknown": { "id": true - } + }, + "after_sensitive": {}, + "before_sensitive": false } } ], diff --git a/command/testdata/show-json/provider-version-no-config/output.json b/command/testdata/show-json/provider-version-no-config/output.json index bef6f9b00..616376331 100644 --- a/command/testdata/show-json/provider-version-no-config/output.json +++ b/command/testdata/show-json/provider-version-no-config/output.json @@ -83,7 +83,9 @@ }, "after": { "ami": "bar" - } + }, + "after_sensitive": {}, + "before_sensitive": false } }, { @@ -103,7 +105,9 @@ }, "after": { "ami": "bar" - } + }, + "after_sensitive": {}, + "before_sensitive": false } }, { @@ -123,7 +127,9 @@ }, "after": { "ami": "bar" - } + }, + "after_sensitive": {}, + "before_sensitive": false } } ], diff --git a/command/testdata/show-json/provider-version/output.json b/command/testdata/show-json/provider-version/output.json index 33807107d..df1540e31 100644 --- a/command/testdata/show-json/provider-version/output.json +++ b/command/testdata/show-json/provider-version/output.json @@ -83,7 +83,9 @@ }, "after": { "ami": "bar" - } + }, + "after_sensitive": {}, + "before_sensitive": false } }, { @@ -103,7 +105,9 @@ }, "after": { "ami": "bar" - } + }, + "after_sensitive": {}, + "before_sensitive": false } }, { @@ -123,7 +127,9 @@ }, "after": { "ami": "bar" - } + }, + "after_sensitive": {}, + "before_sensitive": false } } ], diff --git a/command/testdata/show-json/sensitive-values/main.tf b/command/testdata/show-json/sensitive-values/main.tf new file mode 100644 index 000000000..3f8ba824c --- /dev/null +++ b/command/testdata/show-json/sensitive-values/main.tf @@ -0,0 +1,13 @@ +variable "test_var" { + default = "boop" + sensitive = true +} + +resource "test_instance" "test" { + ami = var.test_var +} + +output "test" { + value = test_instance.test.ami + sensitive = true +} diff --git a/command/testdata/show-json/sensitive-values/output.json b/command/testdata/show-json/sensitive-values/output.json new file mode 100644 index 000000000..7f67bc1fa --- /dev/null +++ b/command/testdata/show-json/sensitive-values/output.json @@ -0,0 +1,114 @@ +{ + "format_version": "0.1", + "variables": { + "test_var": { + "value": "boop" + } + }, + "planned_values": { + "outputs": { + "test": { + "sensitive": true, + "value": "boop" + } + }, + "root_module": { + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_name": "registry.terraform.io/hashicorp/test", + "schema_version": 0, + "values": { + "ami": "boop" + } + } + ] + } + }, + "resource_changes": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "provider_name": "registry.terraform.io/hashicorp/test", + "name": "test", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "boop" + }, + "after_unknown": { + "id": true + }, + "after_sensitive": { + "ami": true + }, + "before_sensitive": false + } + } + ], + "output_changes": { + "test": { + "actions": [ + "create" + ], + "before": null, + "after": "boop", + "after_unknown": false + } + }, + "prior_state": { + "format_version": "0.1", + "values": { + "outputs": { + "test": { + "sensitive": true, + "value": "boop" + } + }, + "root_module": {} + } + }, + "configuration": { + "root_module": { + "outputs": { + "test": { + "expression": { + "references": [ + "test_instance.test" + ] + }, + "sensitive": true + } + }, + "resources": [ + { + "address": "test_instance.test", + "mode": "managed", + "type": "test_instance", + "name": "test", + "provider_config_key": "test", + "schema_version": 0, + "expressions": { + "ami": { + "references": [ + "var.test_var" + ] + } + } + } + ], + "variables": { + "test_var": { + "default": "boop" + } + } + } + } +} From 63613ca1b02a17d76bea4fdfdcd7211d7771da15 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Fri, 26 Mar 2021 13:49:35 -0400 Subject: [PATCH 3/4] command/jsonconfig: Add variable sensitive flag --- command/jsonconfig/config.go | 2 ++ command/testdata/show-json/sensitive-values/output.json | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/command/jsonconfig/config.go b/command/jsonconfig/config.go index d021a8b1f..8575bd25c 100644 --- a/command/jsonconfig/config.go +++ b/command/jsonconfig/config.go @@ -59,6 +59,7 @@ type variables map[string]*variable type variable struct { Default json.RawMessage `json:"default,omitempty"` Description string `json:"description,omitempty"` + Sensitive bool `json:"sensitive,omitempty"` } // Resource is the representation of a resource in the config @@ -263,6 +264,7 @@ func marshalModule(c *configs.Config, schemas *terraform.Schemas, addr string) ( vars[k] = &variable{ Default: defaultValJSON, Description: v.Description, + Sensitive: v.Sensitive, } } module.Variables = vars diff --git a/command/testdata/show-json/sensitive-values/output.json b/command/testdata/show-json/sensitive-values/output.json index 7f67bc1fa..b694fee75 100644 --- a/command/testdata/show-json/sensitive-values/output.json +++ b/command/testdata/show-json/sensitive-values/output.json @@ -106,7 +106,8 @@ ], "variables": { "test_var": { - "default": "boop" + "default": "boop", + "sensitive": true } } } From 5e30d58dc2d06a6b55354dba5cf84b0d85c14d24 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Fri, 26 Mar 2021 19:21:40 -0400 Subject: [PATCH 4/4] command/jsonplan: Add output change sensitivity When an output value changes, we have a small amount of information we can convey about its sensitivity. If either the output was previously marked sensitive, or is currently marked sensitive in the config, this is tracked in the output change data. This commit encodes this boolean in the change struct's `before_sensitive` and `after_sensitive` fields, in the a way which matches resource value sensitivity. Since we have so little information to work with, these two values will always be booleans, and always equal each. This is logically consistent with how else we want to obscure sensitive data: a changing output which was or is marked sensitive should not have the value shown in human-readable output. --- command/jsonplan/plan.go | 23 +++++++++++++++---- .../show-json/basic-create/output.json | 4 +++- .../show-json/basic-delete/output.json | 4 +++- .../show-json/basic-update/output.json | 4 +++- .../testdata/show-json/modules/output.json | 4 +++- .../multi-resource-update/output.json | 4 +++- .../provider-version-no-config/output.json | 4 +++- .../show-json/provider-version/output.json | 4 +++- .../show-json/sensitive-values/output.json | 4 +++- 9 files changed, 43 insertions(+), 12 deletions(-) diff --git a/command/jsonplan/plan.go b/command/jsonplan/plan.go index 07d7f46b9..5a828ccce 100644 --- a/command/jsonplan/plan.go +++ b/command/jsonplan/plan.go @@ -324,13 +324,28 @@ func (p *plan) marshalOutputChanges(changes *plans.Changes) error { } } + // The only information we have in the plan about output sensitivity is + // a boolean which is true if the output was or is marked sensitive. As + // a result, BeforeSensitive and AfterSensitive will be identical, and + // either false or true. + outputSensitive := cty.False + if oc.Sensitive { + outputSensitive = cty.True + } + sensitive, err := ctyjson.Marshal(outputSensitive, outputSensitive.Type()) + if err != nil { + return err + } + a, _ := ctyjson.Marshal(afterUnknown, afterUnknown.Type()) c := change{ - Actions: actionString(oc.Action.String()), - Before: json.RawMessage(before), - After: json.RawMessage(after), - AfterUnknown: a, + Actions: actionString(oc.Action.String()), + Before: json.RawMessage(before), + After: json.RawMessage(after), + AfterUnknown: a, + BeforeSensitive: json.RawMessage(sensitive), + AfterSensitive: json.RawMessage(sensitive), } p.OutputChanges[oc.Addr.OutputValue.Name] = c diff --git a/command/testdata/show-json/basic-create/output.json b/command/testdata/show-json/basic-create/output.json index 01a26d09b..017054bcc 100644 --- a/command/testdata/show-json/basic-create/output.json +++ b/command/testdata/show-json/basic-create/output.json @@ -140,7 +140,9 @@ ], "before": null, "after": "bar", - "after_unknown": false + "after_unknown": false, + "before_sensitive": false, + "after_sensitive": false } }, "configuration": { diff --git a/command/testdata/show-json/basic-delete/output.json b/command/testdata/show-json/basic-delete/output.json index f9efd426f..6b29d785f 100644 --- a/command/testdata/show-json/basic-delete/output.json +++ b/command/testdata/show-json/basic-delete/output.json @@ -81,7 +81,9 @@ ], "before": null, "after": "bar", - "after_unknown": false + "after_unknown": false, + "before_sensitive": false, + "after_sensitive": false } }, "prior_state": { diff --git a/command/testdata/show-json/basic-update/output.json b/command/testdata/show-json/basic-update/output.json index 8a2f4de6f..a6779801f 100644 --- a/command/testdata/show-json/basic-update/output.json +++ b/command/testdata/show-json/basic-update/output.json @@ -61,7 +61,9 @@ ], "before": "bar", "after": "bar", - "after_unknown": false + "after_unknown": false, + "before_sensitive": false, + "after_sensitive": false } }, "prior_state": { diff --git a/command/testdata/show-json/modules/output.json b/command/testdata/show-json/modules/output.json index b78a9d1ab..445f269c2 100644 --- a/command/testdata/show-json/modules/output.json +++ b/command/testdata/show-json/modules/output.json @@ -181,7 +181,9 @@ ], "before": null, "after": "baz", - "after_unknown": false + "after_unknown": false, + "before_sensitive": false, + "after_sensitive": false } }, "configuration": { diff --git a/command/testdata/show-json/multi-resource-update/output.json b/command/testdata/show-json/multi-resource-update/output.json index a0418499f..564a4d713 100644 --- a/command/testdata/show-json/multi-resource-update/output.json +++ b/command/testdata/show-json/multi-resource-update/output.json @@ -98,7 +98,9 @@ ], "before": "bar", "after": "bar", - "after_unknown": false + "after_unknown": false, + "before_sensitive": false, + "after_sensitive": false } }, "prior_state": { diff --git a/command/testdata/show-json/provider-version-no-config/output.json b/command/testdata/show-json/provider-version-no-config/output.json index 616376331..7e0b841f8 100644 --- a/command/testdata/show-json/provider-version-no-config/output.json +++ b/command/testdata/show-json/provider-version-no-config/output.json @@ -140,7 +140,9 @@ ], "before": null, "after": "bar", - "after_unknown": false + "after_unknown": false, + "before_sensitive": false, + "after_sensitive": false } }, "configuration": { diff --git a/command/testdata/show-json/provider-version/output.json b/command/testdata/show-json/provider-version/output.json index df1540e31..eef936ec3 100644 --- a/command/testdata/show-json/provider-version/output.json +++ b/command/testdata/show-json/provider-version/output.json @@ -140,7 +140,9 @@ ], "before": null, "after": "bar", - "after_unknown": false + "after_unknown": false, + "before_sensitive": false, + "after_sensitive": false } }, "configuration": { diff --git a/command/testdata/show-json/sensitive-values/output.json b/command/testdata/show-json/sensitive-values/output.json index b694fee75..51105382a 100644 --- a/command/testdata/show-json/sensitive-values/output.json +++ b/command/testdata/show-json/sensitive-values/output.json @@ -60,7 +60,9 @@ ], "before": null, "after": "boop", - "after_unknown": false + "after_unknown": false, + "before_sensitive": true, + "after_sensitive": true } }, "prior_state": {