diff --git a/models/certificate_properties.pb.go b/models/certificate_properties.pb.go index 7815f28f..71de91b8 100644 --- a/models/certificate_properties.pb.go +++ b/models/certificate_properties.pb.go @@ -25,7 +25,8 @@ var _ = math.Inf const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type CertificateProperties struct { - OrganizationalUnit []string `protobuf:"bytes,1,rep,name=organizational_unit,json=organizationalUnit,proto3" json:"organizational_unit,omitempty"` + OrganizationalUnit []string `protobuf:"bytes,1,rep,name=organizational_unit,json=organizationalUnit,proto3" json:"organizational_unit,omitempty"` + ServiceAccount *ServiceAccount `protobuf:"bytes,2,opt,name=service_account,json=serviceAccount,proto3" json:"service_account,omitempty"` } func (m *CertificateProperties) Reset() { *m = CertificateProperties{} } @@ -67,25 +68,80 @@ func (m *CertificateProperties) GetOrganizationalUnit() []string { return nil } +func (m *CertificateProperties) GetServiceAccount() *ServiceAccount { + if m != nil { + return m.ServiceAccount + } + return nil +} + +type ServiceAccount struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (m *ServiceAccount) Reset() { *m = ServiceAccount{} } +func (*ServiceAccount) ProtoMessage() {} +func (*ServiceAccount) Descriptor() ([]byte, []int) { + return fileDescriptor_9291b57c1fe01997, []int{1} +} +func (m *ServiceAccount) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ServiceAccount) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ServiceAccount.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ServiceAccount) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceAccount.Merge(m, src) +} +func (m *ServiceAccount) XXX_Size() int { + return m.Size() +} +func (m *ServiceAccount) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceAccount.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceAccount proto.InternalMessageInfo + +func (m *ServiceAccount) GetName() string { + if m != nil { + return m.Name + } + return "" +} + func init() { proto.RegisterType((*CertificateProperties)(nil), "models.CertificateProperties") + proto.RegisterType((*ServiceAccount)(nil), "models.ServiceAccount") } func init() { proto.RegisterFile("certificate_properties.proto", fileDescriptor_9291b57c1fe01997) } var fileDescriptor_9291b57c1fe01997 = []byte{ - // 169 bytes of a gzipped FileDescriptorProto + // 232 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0x4e, 0x2d, 0x2a, 0xc9, 0x4c, 0xcb, 0x4c, 0x4e, 0x2c, 0x49, 0x8d, 0x2f, 0x28, 0xca, 0x2f, 0x00, 0x71, 0x53, 0x8b, - 0xf5, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0xd8, 0x72, 0xf3, 0x53, 0x52, 0x73, 0x8a, 0x95, 0x3c, - 0xb8, 0x44, 0x9d, 0x11, 0xea, 0x02, 0xe0, 0xca, 0x84, 0xf4, 0xb9, 0x84, 0xf3, 0x8b, 0xd2, 0x13, - 0xf3, 0x32, 0xab, 0x12, 0x4b, 0x32, 0xf3, 0xf3, 0x12, 0x73, 0xe2, 0x4b, 0xf3, 0x32, 0x4b, 0x24, - 0x18, 0x15, 0x98, 0x35, 0x38, 0x83, 0x84, 0x50, 0xa5, 0x42, 0xf3, 0x32, 0x4b, 0x9c, 0x4c, 0x2e, - 0x3c, 0x94, 0x63, 0xb8, 0xf1, 0x50, 0x8e, 0xe1, 0xc3, 0x43, 0x39, 0xc6, 0x86, 0x47, 0x72, 0x8c, - 0x2b, 0x1e, 0xc9, 0x31, 0x9e, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, - 0x8c, 0x2f, 0x1e, 0xc9, 0x31, 0x7c, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, - 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x9d, 0x63, 0x0c, 0x08, 0x00, 0x00, 0xff, - 0xff, 0x07, 0xe2, 0x02, 0xdf, 0xae, 0x00, 0x00, 0x00, + 0xf5, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0xd8, 0x72, 0xf3, 0x53, 0x52, 0x73, 0x8a, 0x95, 0x3a, + 0x19, 0xb9, 0x44, 0x9d, 0x11, 0x0a, 0x03, 0xe0, 0xea, 0x84, 0xf4, 0xb9, 0x84, 0xf3, 0x8b, 0xd2, + 0x13, 0xf3, 0x32, 0xab, 0x12, 0x4b, 0x32, 0xf3, 0xf3, 0x12, 0x73, 0xe2, 0x4b, 0xf3, 0x32, 0x4b, + 0x24, 0x18, 0x15, 0x98, 0x35, 0x38, 0x83, 0x84, 0x50, 0xa5, 0x42, 0xf3, 0x32, 0x4b, 0x84, 0xec, + 0xb9, 0xf8, 0x8b, 0x53, 0x8b, 0xca, 0x32, 0x93, 0x53, 0xe3, 0x13, 0x93, 0x93, 0xf3, 0x4b, 0xf3, + 0x4a, 0x24, 0x98, 0x14, 0x18, 0x35, 0xb8, 0x8d, 0xc4, 0xf4, 0x20, 0x96, 0xe9, 0x05, 0x43, 0xa4, + 0x1d, 0x21, 0xb2, 0x41, 0x7c, 0xc5, 0x28, 0x7c, 0x25, 0x15, 0x2e, 0x3e, 0x54, 0x15, 0x42, 0x42, + 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x93, + 0xc9, 0x85, 0x87, 0x72, 0x0c, 0x37, 0x1e, 0xca, 0x31, 0x7c, 0x78, 0x28, 0xc7, 0xd8, 0xf0, 0x48, + 0x8e, 0x71, 0xc5, 0x23, 0x39, 0xc6, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, + 0x48, 0x8e, 0xf1, 0xc5, 0x23, 0x39, 0x86, 0x0f, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, + 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0xb0, 0xb7, 0x8d, 0x01, 0x03, 0x00, + 0xf5, 0xbb, 0xa9, 0xe9, 0x16, 0x01, 0x00, 0x00, } func (this *CertificateProperties) Equal(that interface{}) bool { @@ -115,15 +171,55 @@ func (this *CertificateProperties) Equal(that interface{}) bool { return false } } + if !this.ServiceAccount.Equal(that1.ServiceAccount) { + return false + } + return true +} +func (this *ServiceAccount) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*ServiceAccount) + if !ok { + that2, ok := that.(ServiceAccount) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Name != that1.Name { + return false + } return true } func (this *CertificateProperties) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 5) + s := make([]string, 0, 6) s = append(s, "&models.CertificateProperties{") s = append(s, "OrganizationalUnit: "+fmt.Sprintf("%#v", this.OrganizationalUnit)+",\n") + if this.ServiceAccount != nil { + s = append(s, "ServiceAccount: "+fmt.Sprintf("%#v", this.ServiceAccount)+",\n") + } + s = append(s, "}") + return strings.Join(s, "") +} +func (this *ServiceAccount) GoString() string { + if this == nil { + return "nil" + } + s := make([]string, 0, 5) + s = append(s, "&models.ServiceAccount{") + s = append(s, "Name: "+fmt.Sprintf("%#v", this.Name)+",\n") s = append(s, "}") return strings.Join(s, "") } @@ -155,6 +251,18 @@ func (m *CertificateProperties) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.ServiceAccount != nil { + { + size, err := m.ServiceAccount.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCertificateProperties(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } if len(m.OrganizationalUnit) > 0 { for iNdEx := len(m.OrganizationalUnit) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.OrganizationalUnit[iNdEx]) @@ -167,6 +275,36 @@ func (m *CertificateProperties) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ServiceAccount) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ServiceAccount) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ServiceAccount) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintCertificateProperties(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintCertificateProperties(dAtA []byte, offset int, v uint64) int { offset -= sovCertificateProperties(v) base := offset @@ -190,6 +328,23 @@ func (m *CertificateProperties) Size() (n int) { n += 1 + l + sovCertificateProperties(uint64(l)) } } + if m.ServiceAccount != nil { + l = m.ServiceAccount.Size() + n += 1 + l + sovCertificateProperties(uint64(l)) + } + return n +} + +func (m *ServiceAccount) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + if l > 0 { + n += 1 + l + sovCertificateProperties(uint64(l)) + } return n } @@ -205,6 +360,17 @@ func (this *CertificateProperties) String() string { } s := strings.Join([]string{`&CertificateProperties{`, `OrganizationalUnit:` + fmt.Sprintf("%v", this.OrganizationalUnit) + `,`, + `ServiceAccount:` + strings.Replace(this.ServiceAccount.String(), "ServiceAccount", "ServiceAccount", 1) + `,`, + `}`, + }, "") + return s +} +func (this *ServiceAccount) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ServiceAccount{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `}`, }, "") return s @@ -278,6 +444,124 @@ func (m *CertificateProperties) Unmarshal(dAtA []byte) error { } m.OrganizationalUnit = append(m.OrganizationalUnit, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceAccount", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCertificateProperties + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCertificateProperties + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCertificateProperties + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ServiceAccount == nil { + m.ServiceAccount = &ServiceAccount{} + } + if err := m.ServiceAccount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCertificateProperties(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCertificateProperties + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ServiceAccount) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCertificateProperties + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ServiceAccount: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ServiceAccount: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCertificateProperties + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCertificateProperties + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCertificateProperties + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipCertificateProperties(dAtA[iNdEx:]) diff --git a/models/certificate_properties.proto b/models/certificate_properties.proto index 9eced102..3810da01 100644 --- a/models/certificate_properties.proto +++ b/models/certificate_properties.proto @@ -4,5 +4,9 @@ package models; message CertificateProperties { repeated string organizational_unit = 1; + ServiceAccount service_account = 2; } +message ServiceAccount { + string name = 1; +} diff --git a/models/certificate_properties_test.go b/models/certificate_properties_test.go new file mode 100644 index 00000000..eb15c94f --- /dev/null +++ b/models/certificate_properties_test.go @@ -0,0 +1,70 @@ +package models_test + +import ( + "encoding/json" + "strings" + "testing" + + "code.cloudfoundry.org/bbs/models" + "github.com/gogo/protobuf/proto" +) + +func TestServiceAccountCertificatePropertiesRoundTrip(t *testing.T) { + var properties models.CertificateProperties + if err := json.Unmarshal([]byte(`{"organizational_unit":["app:app-guid"],"service_account":{"name":"payments-worker"}}`), &properties); err != nil { + t.Fatal(err) + } + wire, err := proto.Marshal(&properties) + if err != nil { + t.Fatal(err) + } + var decoded models.CertificateProperties + if err := proto.Unmarshal(wire, &decoded); err != nil { + t.Fatal(err) + } + payload, err := json.Marshal(&decoded) + if err != nil { + t.Fatal(err) + } + var result map[string]interface{} + if err := json.Unmarshal(payload, &result); err != nil { + t.Fatal(err) + } + identity, ok := result["service_account"].(map[string]interface{}) + if !ok || identity["name"] != "payments-worker" { + t.Fatalf("service account lost in protobuf round trip: %s", payload) + } +} + +func TestServiceAccountCertificatePropertiesValidation(t *testing.T) { + for _, name := range []string{"", "ab", "Payments-worker", "-payments", "payments-", "payments.reader", "payments.svc.identity", "payments_worker", strings.Repeat("a", 64)} { + t.Run(name, func(t *testing.T) { + properties := &models.CertificateProperties{ServiceAccount: &models.ServiceAccount{Name: name}} + if err := properties.Validate(); err == nil { + t.Fatal("noncanonical service account accepted") + } + runInfo := models.DesiredLRPRunInfo{DesiredLRPKey: models.DesiredLRPKey{ProcessGuid: "process"}, Action: models.WrapAction(&models.RunAction{Path: "/bin/true", User: "vcap"}), CertificateProperties: properties} + if err := runInfo.Validate(); err == nil || !strings.Contains(err.Error(), "service_account") { + t.Fatalf("LRP validation lost service account error: %v", err) + } + task := models.TaskDefinition{RootFs: "preloaded:stack", Action: runInfo.Action, CertificateProperties: properties} + if err := task.Validate(); err == nil || !strings.Contains(err.Error(), "service_account") { + t.Fatalf("task validation lost service account error: %v", err) + } + }) + } + for _, properties := range []*models.CertificateProperties{{}, {ServiceAccount: &models.ServiceAccount{Name: "payments-worker"}}, {ServiceAccount: &models.ServiceAccount{Name: strings.Repeat("a", 63)}}} { + if err := properties.Validate(); err != nil { + t.Fatalf("valid certificate properties rejected: %v", err) + } + } +} + +func TestAssembledDesiredLRPRetainsServiceAccountIdentity(t *testing.T) { + properties := &models.CertificateProperties{ServiceAccount: &models.ServiceAccount{Name: "payments-worker"}} + desired := &models.DesiredLRP{} + desired.AddRunInfo(models.DesiredLRPRunInfo{CertificateProperties: properties}) + if desired.CertificateProperties == nil || desired.CertificateProperties.ServiceAccount.Name != "payments-worker" { + t.Fatal("assembling stored LRP run info dropped account identity") + } +} diff --git a/models/desired_lrp.go b/models/desired_lrp.go index 7783d362..d3beec24 100644 --- a/models/desired_lrp.go +++ b/models/desired_lrp.go @@ -18,6 +18,7 @@ const ExtraRootFSScheme = "extra" const volumeMountedFilesMaxAllowedSize = 1 * 1024 * 1024 // 1MB in bytes var processGuidPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) +var serviceAccountNamePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9-]{1,61}[a-z0-9]\z`) type DesiredLRPChange struct { Before *DesiredLRP @@ -123,6 +124,7 @@ func (desiredLRP *DesiredLRP) AddRunInfo(runInfo DesiredLRPRunInfo) { desiredLRP.TrustedSystemCertificatesPath = runInfo.TrustedSystemCertificatesPath desiredLRP.VolumeMounts = runInfo.VolumeMounts desiredLRP.Network = runInfo.Network + desiredLRP.CertificateProperties = runInfo.CertificateProperties desiredLRP.CheckDefinition = runInfo.CheckDefinition desiredLRP.VolumeMountedFiles = volumeMountedFiles } @@ -786,6 +788,9 @@ func NewDesiredLRPRunInfo( func (runInfo DesiredLRPRunInfo) Validate() error { var validationError ValidationError + if runInfo.CertificateProperties != nil { + validationError = validationError.Check(runInfo.CertificateProperties) + } validationError = validationError.Check(runInfo.DesiredLRPKey) @@ -877,7 +882,10 @@ func (*CertificateProperties) Version() format.Version { return format.V0 } -func (CertificateProperties) Validate() error { +func (properties CertificateProperties) Validate() error { + if properties.ServiceAccount != nil && !serviceAccountNamePattern.MatchString(properties.ServiceAccount.Name) { + return ErrInvalidField{"certificate_properties.service_account.name"} + } return nil } diff --git a/models/task.go b/models/task.go index 2319fe99..d12b4546 100644 --- a/models/task.go +++ b/models/task.go @@ -93,6 +93,9 @@ func (t *TaskDefinition) Copy() *TaskDefinition { func (def *TaskDefinition) Validate() error { var validationError ValidationError + if def.CertificateProperties != nil { + validationError = validationError.Check(def.CertificateProperties) + } if def.RootFs == "" { validationError = validationError.Append(ErrInvalidField{"rootfs"})