Adds controls for impersonation requests. (#6009) (#6073)

Fixes #5352

```yaml
allow:
  impersonate:
    users: ['alice', 'bob']
    roles: ['*']
    where: 'contains(user.spec.traits["groups"], impersonate_role.traits)'
```

Adds "impersonator" to all X.509 and SSH client certs
issued using impersonation and does best effort to track
requests by impersonators in audit events.

Limits certs TTL to the impersonator's max session TTL.

Prevents impersonating users to recursively impersonate
other users.

Allows impersonating users to renew their own certificate,
for example to set route to cluster.

Adds missing token permission for editor role.
This commit is contained in:
Alexander Klizhentas 2021-03-19 16:04:43 -07:00 committed by GitHub
parent 854d5fc80b
commit f17625c1a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 2461 additions and 1023 deletions

View file

@ -129,7 +129,9 @@ type UserMetadata struct {
// User is teleport user name
User string `protobuf:"bytes,1,opt,name=User,proto3" json:"user"`
// Login is OS login
Login string `protobuf:"bytes,2,opt,name=Login,proto3" json:"login,omitempty"`
Login string `protobuf:"bytes,2,opt,name=Login,proto3" json:"login,omitempty"`
// Impersonator is a user acting on behalf of another user
Impersonator string `protobuf:"bytes,3,opt,name=Impersonator,proto3" json:"impersonator,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -3714,264 +3716,266 @@ func init() {
func init() { proto.RegisterFile("events.proto", fileDescriptor_8f22242cb04491f9) }
var fileDescriptor_8f22242cb04491f9 = []byte{
// 4108 bytes of a gzipped FileDescriptorProto
// 4135 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x3c, 0x4d, 0x6f, 0x1c, 0x47,
0x76, 0xf3, 0xcd, 0x99, 0x37, 0x94, 0x44, 0x96, 0x28, 0xa9, 0x25, 0xcb, 0x6a, 0xa9, 0x65, 0xcb,
0x54, 0x2c, 0x8b, 0x11, 0xcd, 0xb5, 0x36, 0x9b, 0x04, 0x36, 0x87, 0xa4, 0x76, 0x18, 0x53, 0x22,
0x5d, 0xa4, 0x76, 0xf7, 0xb2, 0x1e, 0xf4, 0x4c, 0x97, 0xc8, 0x5e, 0xcd, 0x74, 0xcf, 0x76, 0xf7,
0x50, 0xa2, 0x4f, 0xf9, 0xb8, 0xec, 0xc1, 0x01, 0x82, 0xcd, 0x29, 0xc8, 0x21, 0x01, 0x82, 0x1c,
0x12, 0x04, 0x49, 0x90, 0x43, 0x90, 0xbb, 0xb3, 0x81, 0x81, 0x60, 0x93, 0xc5, 0xe6, 0x9c, 0x49,
0x62, 0x20, 0x97, 0x01, 0xf2, 0x07, 0x82, 0x00, 0x59, 0xd4, 0xab, 0xea, 0xee, 0xea, 0x9e, 0x26,
0x65, 0x89, 0x5a, 0x10, 0x94, 0x78, 0x23, 0xdf, 0x57, 0x75, 0xbd, 0x57, 0xf5, 0xaa, 0xde, 0x47,
0x0d, 0x4c, 0xb2, 0x5d, 0xe6, 0x04, 0xfe, 0xed, 0xbe, 0xe7, 0x06, 0x2e, 0xa9, 0x88, 0xff, 0x2e,
0xcd, 0x6c, 0xbb, 0xdb, 0x2e, 0x82, 0xe6, 0xf8, 0x5f, 0x02, 0x7b, 0x49, 0xdf, 0x76, 0xdd, 0xed,
0x2e, 0x9b, 0xc3, 0xff, 0xda, 0x83, 0x47, 0x73, 0x81, 0xdd, 0x63, 0x7e, 0x60, 0xf6, 0xfa, 0x92,
0xe0, 0x72, 0x9a, 0xc0, 0x0f, 0xbc, 0x41, 0x27, 0x10, 0x58, 0xe3, 0x47, 0x05, 0xa8, 0xde, 0x67,
0x81, 0x69, 0x99, 0x81, 0x49, 0x2e, 0x43, 0x79, 0xd5, 0xb1, 0xd8, 0x53, 0x2d, 0x7f, 0x35, 0x3f,
0x5b, 0x6c, 0x54, 0x46, 0x43, 0xbd, 0xc0, 0x6c, 0x2a, 0x80, 0xe4, 0x4d, 0x28, 0x6d, 0xed, 0xf5,
0x99, 0x56, 0xb8, 0x9a, 0x9f, 0xad, 0x35, 0x6a, 0xa3, 0xa1, 0x5e, 0xc6, 0x2f, 0xa3, 0x08, 0x26,
0xd7, 0xa0, 0xb0, 0xba, 0xac, 0x15, 0x11, 0x39, 0x3d, 0x1a, 0xea, 0xa7, 0x06, 0xb6, 0x75, 0xcb,
0xed, 0xd9, 0x01, 0xeb, 0xf5, 0x83, 0x3d, 0x5a, 0x58, 0x5d, 0x26, 0x37, 0xa0, 0xb4, 0xe4, 0x5a,
0x4c, 0x2b, 0x21, 0x11, 0x19, 0x0d, 0xf5, 0xd3, 0x1d, 0xd7, 0x62, 0x0a, 0x15, 0xe2, 0xc9, 0x47,
0x50, 0xda, 0xb2, 0x7b, 0x4c, 0x2b, 0x5f, 0xcd, 0xcf, 0xd6, 0xe7, 0x2f, 0xdd, 0x16, 0x33, 0xb8,
0x1d, 0xce, 0xe0, 0xf6, 0x56, 0x38, 0xc5, 0xc6, 0xd4, 0x97, 0x43, 0x3d, 0x37, 0x1a, 0xea, 0x25,
0x3e, 0xeb, 0x3f, 0xf8, 0x0f, 0x3d, 0x4f, 0x91, 0x93, 0xcc, 0x43, 0x7d, 0xa9, 0x3b, 0xf0, 0x03,
0xe6, 0x3d, 0x30, 0x7b, 0x4c, 0xab, 0xe0, 0x80, 0x53, 0xa3, 0xa1, 0x3e, 0xd9, 0x11, 0xe0, 0x96,
0x63, 0xf6, 0x18, 0x55, 0x89, 0x8c, 0x1f, 0xc0, 0x99, 0x4d, 0xe6, 0xfb, 0xb6, 0xeb, 0x44, 0x0a,
0x79, 0x1b, 0x6a, 0x12, 0xb4, 0xba, 0x8c, 0x4a, 0xa9, 0x35, 0x26, 0x46, 0x43, 0xbd, 0xe8, 0xdb,
0x16, 0x8d, 0x31, 0xe4, 0x57, 0x61, 0xe2, 0xbb, 0x76, 0xb0, 0x73, 0xff, 0xde, 0xa2, 0x54, 0xce,
0xf9, 0xd1, 0x50, 0x27, 0x4f, 0xec, 0x60, 0xa7, 0xd5, 0x7b, 0x64, 0x2a, 0xd3, 0x0b, 0xc9, 0x8c,
0xef, 0xc2, 0xe4, 0x43, 0x9f, 0x79, 0x8a, 0xe6, 0x4b, 0xfc, 0x7f, 0x39, 0x46, 0x95, 0xcf, 0x68,
0xe0, 0x33, 0x8f, 0x22, 0x94, 0xdc, 0x84, 0xf2, 0x9a, 0xbb, 0x6d, 0x3b, 0x52, 0xfa, 0xd9, 0xd1,
0x50, 0x3f, 0xd3, 0xe5, 0x00, 0x45, 0xb4, 0xa0, 0x30, 0xfe, 0xb6, 0x08, 0xa7, 0x37, 0x99, 0xb7,
0xab, 0xc8, 0x5e, 0xe4, 0xf3, 0xe2, 0x10, 0x3e, 0x4b, 0xbf, 0x6f, 0x76, 0x98, 0x1c, 0xe6, 0xc2,
0x68, 0xa8, 0x9f, 0x75, 0x42, 0xa0, 0x22, 0x2b, 0x4d, 0x4f, 0x6e, 0x42, 0x55, 0x80, 0x56, 0x97,
0xe5, 0x37, 0x9c, 0x1a, 0x0d, 0xf5, 0x9a, 0x8f, 0xb0, 0x96, 0x6d, 0xd1, 0x08, 0x4d, 0x56, 0xc2,
0xf1, 0x9b, 0xae, 0x1f, 0x70, 0xe1, 0x72, 0x49, 0xbc, 0x39, 0x1a, 0xea, 0x17, 0x25, 0xc3, 0x8e,
0x44, 0x29, 0x43, 0xa6, 0x98, 0xc8, 0xaf, 0x01, 0x08, 0xc8, 0xa2, 0x65, 0x79, 0x72, 0xc1, 0x5c,
0x1c, 0x0d, 0xf5, 0x73, 0x52, 0x84, 0x69, 0x59, 0x9e, 0xc2, 0xae, 0x10, 0x93, 0x1e, 0x4c, 0x8a,
0xff, 0xd6, 0xcc, 0x36, 0xeb, 0xfa, 0x5a, 0xf9, 0x6a, 0x71, 0xb6, 0x3e, 0x3f, 0x7b, 0x5b, 0x6e,
0xaa, 0xa4, 0x76, 0x6e, 0xab, 0xa4, 0x2b, 0x4e, 0xe0, 0xed, 0x35, 0x74, 0xb9, 0xa6, 0x2e, 0xc8,
0xa1, 0xba, 0x88, 0x53, 0x06, 0x4b, 0x88, 0xbf, 0xf4, 0x21, 0x4c, 0x8f, 0xc9, 0x20, 0x53, 0x50,
0x7c, 0xcc, 0xf6, 0x84, 0x9e, 0x29, 0xff, 0x93, 0xcc, 0x40, 0x79, 0xd7, 0xec, 0x0e, 0xe4, 0xf6,
0xa1, 0xe2, 0x9f, 0x6f, 0x15, 0xbe, 0x99, 0x37, 0xfe, 0x21, 0x0f, 0x64, 0xc9, 0x75, 0x1c, 0xd6,
0x09, 0xd4, 0xb5, 0xf7, 0x01, 0xd4, 0xd6, 0xdc, 0x8e, 0xd9, 0x45, 0x05, 0x08, 0x83, 0x69, 0xa3,
0xa1, 0x3e, 0xc3, 0x67, 0x7e, 0xbb, 0xcb, 0x31, 0xca, 0x27, 0xc5, 0xa4, 0x5c, 0x73, 0x94, 0xf5,
0xdc, 0x80, 0x21, 0x63, 0x21, 0xd6, 0x1c, 0x32, 0x7a, 0x88, 0x52, 0x35, 0x17, 0x13, 0x93, 0x39,
0xa8, 0x6e, 0xf0, 0x3d, 0xd6, 0x71, 0xbb, 0xd2, 0x6a, 0xb8, 0xd4, 0x70, 0xdf, 0x29, 0x2c, 0x11,
0x91, 0xf1, 0xbb, 0x05, 0xb8, 0xf8, 0xf1, 0xa0, 0xcd, 0x3c, 0x87, 0x05, 0xcc, 0x97, 0x9b, 0x29,
0x9a, 0xc1, 0x03, 0x98, 0x1e, 0x43, 0xca, 0x99, 0x5c, 0x1d, 0x0d, 0xf5, 0xcb, 0x8f, 0x23, 0x64,
0x4b, 0xee, 0x4a, 0x65, 0x90, 0x71, 0x56, 0xd2, 0x84, 0x33, 0x31, 0x90, 0x6f, 0x0c, 0x5f, 0x2b,
0x5c, 0x2d, 0xce, 0xd6, 0x1a, 0x57, 0x46, 0x43, 0xfd, 0x92, 0x22, 0x8d, 0x6f, 0x1d, 0xd5, 0x60,
0x69, 0x36, 0xf2, 0x31, 0x4c, 0xc5, 0xa0, 0x6f, 0x7b, 0xee, 0xa0, 0xef, 0x6b, 0x45, 0x14, 0xa5,
0x8f, 0x86, 0xfa, 0x1b, 0x8a, 0xa8, 0x6d, 0x44, 0x2a, 0xb2, 0xc6, 0x18, 0x8d, 0xff, 0x2e, 0xc2,
0xb9, 0x18, 0xb8, 0xe1, 0x5a, 0x91, 0x02, 0xd6, 0x55, 0x05, 0x6c, 0xb8, 0x16, 0xfa, 0x22, 0xa1,
0x80, 0x6b, 0xa3, 0xa1, 0xfe, 0xa6, 0x32, 0x4e, 0xdf, 0xb5, 0x5a, 0xa9, 0x2d, 0x31, 0xce, 0x4b,
0x3e, 0x85, 0xf3, 0x63, 0x40, 0xb1, 0xa3, 0x85, 0x9d, 0x6f, 0x8c, 0x86, 0xba, 0x91, 0x21, 0x35,
0xbd, 0xc1, 0xf7, 0x91, 0x42, 0x4c, 0xb8, 0xa0, 0xa8, 0xdd, 0x75, 0x02, 0xd3, 0x76, 0xa4, 0x0b,
0x15, 0xeb, 0xe1, 0x9d, 0xd1, 0x50, 0xbf, 0xae, 0xda, 0x2d, 0xa4, 0x49, 0x7f, 0xfc, 0x7e, 0x72,
0x88, 0x05, 0x5a, 0x06, 0x6a, 0xb5, 0x67, 0x6e, 0x87, 0xe7, 0xc2, 0xec, 0x68, 0xa8, 0xbf, 0x95,
0x39, 0x86, 0xcd, 0xa9, 0x94, 0x41, 0xf6, 0x95, 0x44, 0x28, 0x90, 0x18, 0xf7, 0xc0, 0xb5, 0x18,
0xce, 0xa1, 0x8c, 0xf2, 0x8d, 0xd1, 0x50, 0xbf, 0xa2, 0xc8, 0x77, 0x5c, 0x8b, 0xa5, 0x3f, 0x3f,
0x83, 0xdb, 0xf8, 0xbf, 0x12, 0x77, 0x2c, 0xe8, 0xf3, 0x37, 0x03, 0xd3, 0x0b, 0xc8, 0xb7, 0xe2,
0xa3, 0x13, 0xad, 0x5a, 0x9f, 0x9f, 0x0a, 0x9d, 0x4c, 0x08, 0x6f, 0x4c, 0x72, 0x67, 0xf2, 0xb3,
0xa1, 0x9e, 0x1f, 0x0d, 0xf5, 0x1c, 0xad, 0x2a, 0xbb, 0x5b, 0x38, 0xfc, 0x02, 0xf2, 0xcd, 0x84,
0x7c, 0xea, 0xa1, 0x90, 0xe2, 0x15, 0x47, 0xc1, 0x87, 0x30, 0x21, 0xbf, 0x01, 0x2d, 0x52, 0x9f,
0xbf, 0x10, 0xfb, 0xb5, 0xc4, 0xd9, 0x95, 0xe2, 0x0e, 0xb9, 0xc8, 0x6f, 0x40, 0x45, 0xb8, 0x2b,
0xd4, 0x76, 0x7d, 0xfe, 0x7c, 0xb6, 0x5f, 0x4c, 0xb1, 0x4b, 0x1e, 0xd2, 0x04, 0x88, 0x5d, 0x55,
0x74, 0x3e, 0x4b, 0x09, 0xe3, 0x4e, 0x2c, 0x25, 0x45, 0xe1, 0x25, 0x1f, 0xc0, 0xe4, 0x16, 0xf3,
0x7a, 0xb6, 0x63, 0x76, 0x37, 0xed, 0xcf, 0xc2, 0x23, 0x1a, 0xef, 0x04, 0xbe, 0xfd, 0x99, 0x6a,
0x8b, 0x04, 0x1d, 0xf9, 0x7e, 0x96, 0x53, 0x99, 0xc0, 0x0f, 0xb9, 0x16, 0x7e, 0xc8, 0xbe, 0x2e,
0x29, 0xf5, 0x3d, 0x19, 0x3e, 0xe6, 0x13, 0x38, 0x95, 0xd8, 0x1b, 0x5a, 0x15, 0x45, 0xbf, 0x39,
0x2e, 0x5a, 0xd9, 0xe8, 0x29, 0xb1, 0x49, 0x09, 0xfc, 0x44, 0x5c, 0x75, 0xec, 0xc0, 0x36, 0xbb,
0x4b, 0x6e, 0xaf, 0x67, 0x3a, 0x96, 0x56, 0x43, 0x57, 0x83, 0x27, 0xa2, 0x2d, 0x30, 0xad, 0x8e,
0x40, 0xa9, 0x27, 0x62, 0x92, 0xc9, 0xf8, 0x8b, 0x22, 0xd4, 0xa5, 0x11, 0x7f, 0xcb, 0xb5, 0x9d,
0x93, 0xd5, 0x77, 0x98, 0xd5, 0x97, 0xb9, 0x8a, 0x2a, 0x2f, 0x6b, 0x15, 0x19, 0x9f, 0x17, 0x22,
0x57, 0xb1, 0xe1, 0xd9, 0xce, 0xe1, 0x5c, 0xc5, 0x0d, 0x80, 0xa5, 0x9d, 0x81, 0xf3, 0x58, 0x5c,
0xcd, 0x0b, 0xf1, 0xd5, 0xbc, 0x63, 0x53, 0x05, 0xc3, 0xef, 0xe7, 0xcb, 0x5c, 0x3e, 0xb7, 0xcc,
0x64, 0xa3, 0xf6, 0xa5, 0x90, 0x94, 0x7f, 0x8f, 0x22, 0x98, 0xe8, 0x50, 0x6e, 0xec, 0x05, 0xcc,
0x47, 0xcd, 0x17, 0xc5, 0xfd, 0xbd, 0xcd, 0x01, 0x54, 0xc0, 0xc9, 0x02, 0x4c, 0x2f, 0xb3, 0xae,
0xb9, 0x77, 0xdf, 0xee, 0x76, 0x6d, 0x9f, 0x75, 0x5c, 0xc7, 0xf2, 0x51, 0xc9, 0x72, 0xb8, 0x9e,
0x4f, 0xc7, 0x09, 0x88, 0x01, 0x95, 0xf5, 0x47, 0x8f, 0x7c, 0x16, 0xa0, 0xfa, 0x8a, 0x0d, 0x18,
0x0d, 0xf5, 0x8a, 0x8b, 0x10, 0x2a, 0x31, 0xc6, 0xcf, 0x0b, 0x70, 0x4a, 0xaa, 0x83, 0xb2, 0x1f,
0xb0, 0xce, 0xd1, 0xb8, 0xce, 0x78, 0xed, 0x15, 0x0f, 0xbd, 0xf6, 0x4a, 0x87, 0x58, 0x7b, 0x06,
0x54, 0x28, 0x33, 0x7d, 0xb9, 0x82, 0x6b, 0x42, 0x63, 0x1e, 0x42, 0xa8, 0xc4, 0x90, 0x6b, 0x30,
0x71, 0xdf, 0x7c, 0x6a, 0xf7, 0x06, 0x3d, 0xa9, 0x56, 0x0c, 0x3b, 0x7a, 0xe6, 0x53, 0x1a, 0xc2,
0x8d, 0xbf, 0x29, 0x71, 0x39, 0xdc, 0x57, 0x1e, 0x4f, 0x57, 0xf0, 0xf2, 0x14, 0x1a, 0x1b, 0xb6,
0xfc, 0x02, 0x86, 0x7d, 0x6d, 0x0e, 0x22, 0xe3, 0x2f, 0x27, 0x78, 0x50, 0x85, 0xda, 0x5f, 0x71,
0xac, 0x93, 0x55, 0x73, 0x98, 0x55, 0xb3, 0x0c, 0xd3, 0x2b, 0xce, 0x8e, 0xe9, 0x74, 0x98, 0x45,
0x59, 0xc7, 0xf5, 0x2c, 0xdb, 0xd9, 0xc6, 0xa5, 0x53, 0x15, 0xc1, 0x3f, 0x93, 0xc8, 0x96, 0x17,
0x62, 0xe9, 0x38, 0x03, 0xb9, 0x03, 0xf5, 0x55, 0x27, 0x60, 0x9e, 0xd9, 0x09, 0xec, 0x5d, 0x86,
0xab, 0xa7, 0xda, 0x38, 0x33, 0x1a, 0xea, 0x75, 0x3b, 0x06, 0x53, 0x95, 0x86, 0x2c, 0xc0, 0xe4,
0x86, 0xe9, 0x05, 0x76, 0xc7, 0xee, 0x9b, 0x4e, 0xe0, 0x6b, 0x55, 0xbc, 0x4b, 0x60, 0x6a, 0xa3,
0xaf, 0xc0, 0x69, 0x82, 0x8a, 0x7c, 0x1f, 0x6a, 0x78, 0x67, 0xc5, 0xb4, 0x4a, 0xed, 0x99, 0x69,
0x95, 0xeb, 0x71, 0x08, 0x8c, 0x6a, 0x6f, 0xf9, 0x9c, 0x39, 0xde, 0x0a, 0x98, 0x69, 0x89, 0x25,
0x92, 0xef, 0xc1, 0xc4, 0x8a, 0x63, 0xa1, 0x70, 0x78, 0xa6, 0x70, 0x43, 0x0a, 0x3f, 0x1f, 0x0b,
0x77, 0xfb, 0x29, 0xd9, 0xa1, 0xb8, 0xec, 0x5d, 0x56, 0xff, 0xe5, 0xed, 0xb2, 0xc9, 0x5f, 0xc2,
0x75, 0xef, 0xd4, 0x8b, 0x5c, 0xf7, 0x3e, 0x83, 0x7a, 0x63, 0xe3, 0x5e, 0xb4, 0xe1, 0x2e, 0x42,
0x71, 0x43, 0xe6, 0xa0, 0x4a, 0xe2, 0x30, 0xe8, 0xdb, 0x16, 0xe5, 0x30, 0x72, 0x13, 0xaa, 0x4b,
0x18, 0xa6, 0xca, 0xe4, 0x4c, 0x49, 0x24, 0x67, 0x3a, 0x08, 0xc3, 0xe4, 0x4c, 0x88, 0x26, 0x6f,
0xc3, 0xc4, 0x86, 0xe7, 0x6e, 0x7b, 0x66, 0x4f, 0xc6, 0x73, 0xf5, 0xd1, 0x50, 0x9f, 0xe8, 0x0b,
0x10, 0x0d, 0x71, 0xc6, 0x1f, 0xe6, 0xa1, 0xb2, 0x19, 0x98, 0xc1, 0xc0, 0xe7, 0x1c, 0x9b, 0x83,
0x4e, 0x87, 0xf9, 0x3e, 0x8e, 0x5d, 0x15, 0x1c, 0xbe, 0x00, 0xd1, 0x10, 0x47, 0x6e, 0x42, 0x79,
0xc5, 0xf3, 0x5c, 0x4f, 0xcd, 0x50, 0x31, 0x0e, 0x50, 0x33, 0x54, 0x48, 0x41, 0xee, 0x42, 0x5d,
0xb8, 0x09, 0xdf, 0xe7, 0x31, 0x9f, 0xf8, 0x8e, 0x73, 0xa3, 0xa1, 0x3e, 0xdd, 0x13, 0x20, 0x85,
0x45, 0xa5, 0x34, 0xbe, 0xc0, 0xd4, 0x16, 0x2e, 0x19, 0xa9, 0xa4, 0xd7, 0xf1, 0x0e, 0xfc, 0x3e,
0x14, 0x1b, 0x1b, 0xf7, 0xa4, 0xcf, 0x3a, 0x1b, 0xb2, 0x2a, 0x4b, 0x25, 0xc5, 0xc7, 0xa9, 0xc9,
0x65, 0x28, 0x6d, 0xf0, 0xe5, 0x53, 0xc1, 0xe5, 0x81, 0xe9, 0xc5, 0x3e, 0x5f, 0x3f, 0x08, 0x45,
0xac, 0x19, 0xec, 0xa0, 0xfb, 0x91, 0xc9, 0xc7, 0xbe, 0x19, 0xec, 0x50, 0x84, 0x72, 0xec, 0xa2,
0xb7, 0xbd, 0x2b, 0x1d, 0x0d, 0x62, 0x4d, 0x6f, 0x7b, 0x97, 0x22, 0x94, 0xcc, 0x01, 0x50, 0x16,
0x0c, 0x3c, 0x07, 0x13, 0xbb, 0xdc, 0xb3, 0x94, 0x85, 0x03, 0xf3, 0x10, 0xda, 0xea, 0xb8, 0x16,
0xa3, 0x0a, 0x89, 0xf1, 0xe7, 0x71, 0x18, 0xb3, 0x6c, 0xfb, 0x8f, 0x4f, 0x4c, 0xf8, 0x1c, 0x26,
0xe4, 0x46, 0xaa, 0x64, 0x1a, 0x49, 0x87, 0xf2, 0xbd, 0xae, 0xb9, 0xed, 0xa3, 0x0d, 0xcb, 0xe2,
0x72, 0xff, 0x88, 0x03, 0xa8, 0x80, 0xa7, 0xec, 0x54, 0x7d, 0xb6, 0x9d, 0xfe, 0x3d, 0xde, 0x6d,
0x0f, 0x58, 0xf0, 0xc4, 0xf5, 0x4e, 0x4c, 0xf5, 0x75, 0x4d, 0x75, 0x03, 0x26, 0x36, 0xbd, 0x0e,
0xa6, 0x5f, 0x85, 0xb5, 0x26, 0x47, 0x43, 0xbd, 0xea, 0x7b, 0x1d, 0xcc, 0x5a, 0xd3, 0x10, 0xc9,
0xe9, 0x96, 0xfd, 0x00, 0xe9, 0x26, 0x62, 0x3a, 0xcb, 0x0f, 0x24, 0x9d, 0x44, 0x4a, 0xba, 0x0d,
0xd7, 0x0b, 0xa4, 0xe1, 0x22, 0xba, 0xbe, 0xeb, 0x05, 0x34, 0x44, 0x92, 0x77, 0x01, 0xb6, 0x96,
0x36, 0xbe, 0xc3, 0x3c, 0x54, 0x97, 0xd8, 0x8b, 0xe8, 0xae, 0x77, 0x05, 0x88, 0x2a, 0x68, 0xe3,
0xaf, 0x94, 0x7d, 0xc8, 0x0d, 0x74, 0x92, 0x4e, 0x38, 0xc4, 0x5d, 0x72, 0x1e, 0xa6, 0x30, 0x86,
0xde, 0xf2, 0x4c, 0xc7, 0xef, 0xd9, 0x41, 0xc0, 0x2c, 0xe9, 0x6b, 0x31, 0x72, 0x0e, 0x9e, 0xd2,
0x31, 0x3c, 0xb9, 0x05, 0xa7, 0x10, 0x46, 0x59, 0x87, 0xd9, 0xbb, 0xcc, 0xc2, 0x35, 0x20, 0x19,
0xbc, 0xa7, 0x34, 0x89, 0x34, 0xfe, 0x39, 0xce, 0x28, 0xac, 0x31, 0x73, 0x97, 0x9d, 0xd8, 0xeb,
0x10, 0xf6, 0x32, 0xfe, 0xac, 0x08, 0x35, 0x3e, 0x23, 0xac, 0x99, 0x1d, 0x89, 0x2a, 0x17, 0xc2,
0x1b, 0x96, 0xd4, 0xe4, 0xe9, 0x48, 0x13, 0x08, 0x1d, 0xd3, 0x80, 0xb8, 0x8d, 0xdd, 0x82, 0xca,
0x7d, 0x16, 0xec, 0xb8, 0x96, 0x4c, 0x95, 0xcf, 0x8c, 0x86, 0xfa, 0x54, 0x0f, 0x21, 0xca, 0xad,
0x49, 0xd2, 0x90, 0xc7, 0x40, 0x56, 0x2d, 0xe6, 0x04, 0x76, 0xb0, 0xb7, 0x18, 0x04, 0x9e, 0xdd,
0x1e, 0x04, 0xcc, 0x97, 0x7a, 0xbb, 0x30, 0x76, 0x41, 0xdf, 0xc4, 0xb2, 0x30, 0x66, 0xc7, 0x67,
0xcc, 0x88, 0x3c, 0x16, 0xfb, 0xbf, 0x43, 0xbd, 0x22, 0x68, 0x68, 0x86, 0x58, 0xf2, 0x09, 0xd4,
0xee, 0xdf, 0x5b, 0x5c, 0x66, 0xbb, 0x76, 0x87, 0xc9, 0x4c, 0xda, 0xc5, 0x48, 0x8b, 0x21, 0x22,
0x52, 0x09, 0x56, 0xb2, 0x7a, 0x8f, 0xcc, 0x96, 0x85, 0x70, 0xb5, 0x92, 0x15, 0x11, 0x1b, 0x5f,
0xe5, 0x61, 0x8a, 0x32, 0xdf, 0x1d, 0x78, 0x31, 0x27, 0xb9, 0x01, 0x25, 0xa5, 0x8c, 0x82, 0x61,
0x7a, 0x2a, 0x77, 0x8f, 0x78, 0xb2, 0x0a, 0x13, 0x2b, 0x4f, 0xfb, 0xb6, 0xc7, 0x7c, 0x69, 0x9b,
0x83, 0x42, 0x92, 0xb3, 0x32, 0x24, 0x99, 0x60, 0x82, 0x45, 0xc6, 0x20, 0xe2, 0x1f, 0xf2, 0x01,
0xd4, 0x1e, 0xf6, 0x2d, 0x33, 0x60, 0x56, 0x63, 0x4f, 0xde, 0x57, 0xf1, 0xfb, 0x07, 0x02, 0xd8,
0x6a, 0xef, 0xa9, 0xdf, 0x1f, 0x91, 0x92, 0xeb, 0x50, 0xdc, 0xda, 0x5a, 0x93, 0xa6, 0xc2, 0x92,
0x78, 0x10, 0xa8, 0x45, 0x3b, 0x8e, 0x35, 0x7e, 0x5c, 0x00, 0xe0, 0x2b, 0x62, 0xc9, 0x63, 0x66,
0x70, 0x34, 0xdb, 0xba, 0x01, 0xd5, 0x50, 0xcd, 0x72, 0x35, 0x6a, 0x21, 0x6f, 0x5a, 0xfd, 0xe9,
0xb1, 0x43, 0x3c, 0xbf, 0x80, 0x50, 0xb7, 0x8b, 0xd9, 0xc5, 0x62, 0xd8, 0x1d, 0xe0, 0x71, 0x00,
0x15, 0x70, 0xf2, 0x2e, 0xd4, 0xe4, 0x06, 0x74, 0x3d, 0x99, 0xf8, 0x12, 0x61, 0x4a, 0x08, 0xa4,
0x31, 0xde, 0xf8, 0xc7, 0xbc, 0x50, 0xca, 0x32, 0xeb, 0xb2, 0xe3, 0xab, 0x14, 0xe3, 0x47, 0x79,
0x20, 0x5c, 0xd8, 0x86, 0xe9, 0xfb, 0x4f, 0x5c, 0xcf, 0x5a, 0xda, 0x31, 0x9d, 0xed, 0x23, 0x99,
0x8e, 0xf1, 0xe3, 0x12, 0x9c, 0x5d, 0x14, 0x41, 0x1b, 0xfb, 0xe1, 0x80, 0xf9, 0xc1, 0x31, 0x5f,
0x6f, 0x37, 0x93, 0xeb, 0x0d, 0x03, 0x4e, 0x5c, 0x6f, 0x6a, 0xc0, 0x29, 0x56, 0xde, 0x5b, 0x50,
0x93, 0x73, 0x5e, 0x5d, 0x96, 0x2b, 0x0f, 0x0f, 0x59, 0xdb, 0xa2, 0x31, 0x82, 0xbc, 0x07, 0x93,
0xf2, 0x1f, 0xee, 0x6b, 0xc3, 0x34, 0x20, 0xae, 0x63, 0x9f, 0x03, 0x68, 0x02, 0x4d, 0xbe, 0x01,
0x35, 0xbe, 0x38, 0xb7, 0x4d, 0xbe, 0x9c, 0x27, 0xe2, 0x76, 0x0a, 0x2b, 0x04, 0xaa, 0x2e, 0x21,
0xa2, 0xe4, 0x0e, 0x5c, 0xe6, 0x7e, 0xab, 0xb1, 0x03, 0x17, 0xb9, 0x5f, 0xd5, 0x81, 0xcb, 0x2c,
0xf0, 0xa7, 0x50, 0x5f, 0x74, 0x1c, 0x37, 0x30, 0xf9, 0xa1, 0xe5, 0xcb, 0xbc, 0xcd, 0xbe, 0x9e,
0xfb, 0x3a, 0x16, 0xf9, 0x63, 0xfa, 0x4c, 0xd7, 0xad, 0x0a, 0x34, 0xfe, 0xa4, 0x00, 0x75, 0x7e,
0x73, 0xbc, 0xe7, 0x7a, 0x4f, 0x4c, 0xef, 0x68, 0xc2, 0xe9, 0xe4, 0xa1, 0x5e, 0x3c, 0xc4, 0x25,
0x2c, 0x3e, 0x52, 0x4b, 0xcf, 0x71, 0xa4, 0xf2, 0xf0, 0x96, 0xdf, 0xc0, 0xcb, 0x71, 0x5c, 0x85,
0xb7, 0x6f, 0x84, 0x1a, 0xbf, 0x5d, 0x00, 0xf8, 0xde, 0x9d, 0x3b, 0xaf, 0xb1, 0x82, 0x8c, 0x3f,
0xce, 0xc3, 0x19, 0x99, 0x6f, 0x51, 0xfa, 0xa2, 0x26, 0xc2, 0xe4, 0x56, 0x3e, 0xce, 0x23, 0xc9,
0xa4, 0x16, 0x0d, 0x71, 0x64, 0x1e, 0xaa, 0x2b, 0x4f, 0xed, 0x00, 0x43, 0x4e, 0xa5, 0x31, 0x8a,
0x49, 0x98, 0xda, 0x52, 0x12, 0xd2, 0x91, 0xf7, 0xc2, 0x4c, 0x52, 0x31, 0xde, 0x54, 0x9c, 0x61,
0x25, 0x33, 0x9b, 0x64, 0xfc, 0x7d, 0x09, 0x4a, 0x2b, 0x4f, 0x59, 0xe7, 0x98, 0x9b, 0x46, 0xb9,
0x59, 0x97, 0x0e, 0x79, 0xb3, 0x7e, 0x91, 0x6c, 0xf6, 0x87, 0xb1, 0x3d, 0x2b, 0xc9, 0xe1, 0x53,
0x96, 0x4f, 0x0f, 0x1f, 0x5a, 0xfa, 0xf8, 0x15, 0x43, 0x7e, 0x52, 0x84, 0xe2, 0xe6, 0xd2, 0xc6,
0xc9, 0xba, 0x39, 0xd2, 0x75, 0x73, 0x70, 0xea, 0xd1, 0x80, 0xca, 0xa2, 0xd0, 0x51, 0x35, 0xae,
0x94, 0x9a, 0x08, 0xa1, 0x12, 0x63, 0x7c, 0x5e, 0x80, 0xda, 0xe6, 0xa0, 0xed, 0xef, 0xf9, 0x01,
0xeb, 0x1d, 0x73, 0x6b, 0x5e, 0x96, 0xb1, 0x4d, 0x29, 0xd6, 0x06, 0xb6, 0xa9, 0x8a, 0x88, 0xe6,
0x7a, 0xe8, 0x19, 0x95, 0xdb, 0x73, 0xe4, 0x19, 0x43, 0x7f, 0xf8, 0x77, 0x05, 0x98, 0x5a, 0xea,
0xda, 0xcc, 0x09, 0x96, 0x6d, 0x5f, 0xde, 0xad, 0x8f, 0xb9, 0x56, 0x0e, 0x97, 0x34, 0xf8, 0x1a,
0xd5, 0x76, 0xe3, 0x77, 0x0a, 0x50, 0x5f, 0x1c, 0x04, 0x3b, 0x8b, 0x01, 0x1e, 0x2e, 0xaf, 0xe5,
0x31, 0xff, 0xf3, 0x3c, 0x68, 0x94, 0xf9, 0x2c, 0x08, 0x83, 0x95, 0x2d, 0xf7, 0x31, 0x73, 0x5e,
0x42, 0x94, 0xa0, 0xde, 0xf6, 0x0b, 0x2f, 0x78, 0xdb, 0x0f, 0x95, 0x5a, 0x7c, 0xce, 0xa8, 0x87,
0xc7, 0x91, 0x3c, 0x08, 0x78, 0x45, 0xa6, 0xf1, 0x12, 0xc2, 0xe1, 0xa3, 0x9c, 0xc6, 0xbf, 0xe4,
0x61, 0x66, 0xcb, 0xe3, 0x27, 0xba, 0x25, 0x0f, 0xf6, 0x63, 0x6e, 0x97, 0xf1, 0x09, 0x1d, 0x73,
0x0b, 0xfd, 0x5b, 0x1e, 0x2e, 0x26, 0x27, 0xf4, 0x2a, 0x78, 0x81, 0x7f, 0xcd, 0xc3, 0xb9, 0x6f,
0xdb, 0xc1, 0xce, 0xa0, 0x1d, 0x65, 0x98, 0x5e, 0xbd, 0x19, 0x1d, 0xf3, 0x95, 0xf7, 0xd3, 0x3c,
0x9c, 0x5d, 0x5f, 0x5d, 0x5e, 0x7a, 0x55, 0x2c, 0x34, 0x36, 0x9f, 0x57, 0xc0, 0x3e, 0x9b, 0x8b,
0xf7, 0xd7, 0x5e, 0x25, 0xfb, 0x24, 0xe6, 0x73, 0xcc, 0xed, 0xf3, 0x7b, 0x15, 0xa8, 0xf3, 0x00,
0x57, 0x26, 0x29, 0x5f, 0xeb, 0x2b, 0xff, 0x3c, 0xd4, 0xa5, 0x1a, 0x30, 0xb6, 0x2c, 0xc7, 0x8f,
0xff, 0x3c, 0x01, 0x6e, 0x61, 0x8c, 0xa9, 0x12, 0xf1, 0xd0, 0xeb, 0x3b, 0xcc, 0x6b, 0xab, 0xed,
0x15, 0xbb, 0xcc, 0x6b, 0x53, 0x84, 0x92, 0xb5, 0xb8, 0x10, 0xb5, 0xb8, 0xb1, 0x8a, 0xef, 0x7e,
0x64, 0xc8, 0x8a, 0x0f, 0x99, 0x3c, 0x89, 0x6b, 0x99, 0x7d, 0x5b, 0xbc, 0x18, 0x52, 0x1f, 0x0c,
0xa5, 0x39, 0xc9, 0x03, 0x98, 0x0e, 0x61, 0xf1, 0x03, 0x9e, 0x6a, 0x86, 0xb8, 0xac, 0xa7, 0x3b,
0xe3, 0xac, 0xe4, 0x43, 0x98, 0x0c, 0x81, 0x1f, 0xdb, 0xf8, 0xbc, 0x80, 0x8b, 0x7a, 0x63, 0x34,
0xd4, 0x2f, 0x44, 0xa2, 0x1e, 0xdb, 0x89, 0x6e, 0xb3, 0x04, 0x83, 0x2a, 0x00, 0xe3, 0x4f, 0xc8,
0x10, 0x90, 0x2a, 0xb2, 0x25, 0x18, 0xc8, 0x37, 0x50, 0x40, 0xdf, 0x75, 0x7c, 0x86, 0xc9, 0xbe,
0x3a, 0xf6, 0x1e, 0x60, 0xc9, 0xcb, 0x93, 0x70, 0xd1, 0x61, 0x92, 0x20, 0x23, 0xeb, 0x00, 0x71,
0x52, 0x46, 0xb6, 0xde, 0x3d, 0x77, 0xba, 0x48, 0x11, 0x61, 0xfc, 0x7f, 0x01, 0xce, 0x2c, 0xf6,
0xfb, 0x27, 0xaf, 0x74, 0x5e, 0x56, 0x63, 0xc3, 0x1c, 0xc0, 0xc6, 0xa0, 0xdd, 0xb5, 0x3b, 0x4a,
0x97, 0x0a, 0xb6, 0x0d, 0xf5, 0x11, 0x2a, 0x1a, 0x55, 0x14, 0x12, 0xe3, 0xf3, 0xa2, 0x6a, 0x01,
0x7c, 0x9d, 0x70, 0x62, 0x81, 0xf2, 0xa1, 0x5c, 0xe1, 0x69, 0x55, 0x99, 0xb2, 0x89, 0x4f, 0x56,
0x8e, 0xc2, 0x0e, 0xda, 0x0e, 0x47, 0xb5, 0x6c, 0x8b, 0xa6, 0x68, 0x8d, 0xff, 0xc9, 0xc3, 0x74,
0x6c, 0x8e, 0x97, 0x71, 0x38, 0xcc, 0x01, 0x88, 0x8c, 0x41, 0x94, 0xd5, 0x3f, 0x25, 0x56, 0x84,
0x8f, 0x50, 0xd9, 0x48, 0x16, 0x93, 0x44, 0x29, 0xbe, 0x62, 0x66, 0x8a, 0xef, 0x26, 0x54, 0xa9,
0xf9, 0xe4, 0x93, 0x01, 0xf3, 0xf6, 0x64, 0xda, 0x0b, 0xf3, 0x5a, 0x9e, 0xf9, 0xa4, 0xf5, 0x43,
0x0e, 0xa4, 0x11, 0x9a, 0x18, 0x51, 0xf3, 0x83, 0x92, 0xc9, 0x11, 0xcd, 0x0f, 0x61, 0xcb, 0x83,
0xf1, 0x47, 0x05, 0x98, 0x5a, 0x36, 0x03, 0xb3, 0x6d, 0xfa, 0x71, 0xcb, 0xc0, 0x37, 0xe1, 0x4c,
0x08, 0xe3, 0xe6, 0xb1, 0xa3, 0x07, 0xd0, 0xa7, 0x47, 0x43, 0x1d, 0xac, 0x76, 0xcb, 0x17, 0x50,
0x9a, 0x26, 0x23, 0xbf, 0x1e, 0x4b, 0x8b, 0x1e, 0xc6, 0x16, 0xe2, 0x4d, 0x60, 0xb5, 0x5b, 0x7d,
0x09, 0xa6, 0x63, 0x84, 0xe4, 0x16, 0xd4, 0x43, 0xd8, 0x43, 0xba, 0x2a, 0xe7, 0x8f, 0x1f, 0x6d,
0xb5, 0x5b, 0x03, 0xcf, 0xa6, 0x2a, 0x9a, 0xcc, 0xc1, 0x64, 0xf8, 0xaf, 0x92, 0x03, 0xc4, 0xba,
0x8a, 0xd5, 0x16, 0xaf, 0xd5, 0x13, 0x04, 0x2a, 0x03, 0xee, 0x90, 0x72, 0x82, 0x01, 0x5f, 0x8f,
0x27, 0x08, 0x8c, 0x9f, 0x16, 0x61, 0x26, 0x9e, 0xe0, 0x89, 0x87, 0x7c, 0x39, 0xfb, 0x33, 0xce,
0xb6, 0x55, 0x9e, 0xa3, 0xea, 0xd8, 0x80, 0x6a, 0x68, 0x0a, 0x59, 0x26, 0x89, 0xae, 0x8a, 0xe9,
0xe5, 0x9b, 0x56, 0x7d, 0x88, 0x37, 0xbe, 0x28, 0x8c, 0xd9, 0x53, 0x6c, 0x94, 0x63, 0x69, 0x4f,
0x55, 0x23, 0xa5, 0x17, 0xd3, 0x08, 0x99, 0x87, 0x53, 0xe1, 0xdf, 0xc2, 0xa3, 0x94, 0x95, 0xb6,
0xca, 0xb6, 0x74, 0x28, 0x49, 0x12, 0xe3, 0xf7, 0x0b, 0x40, 0x52, 0x5a, 0x3c, 0xb6, 0x8f, 0x63,
0x5e, 0x82, 0x0e, 0x8d, 0xbf, 0xce, 0xc3, 0xf4, 0x58, 0xbf, 0x16, 0x79, 0x1f, 0x40, 0x40, 0x94,
0xde, 0x2b, 0xec, 0xb9, 0x88, 0x7b, 0xb8, 0x84, 0x8f, 0x52, 0xc8, 0xc8, 0x1c, 0x54, 0xc5, 0x7f,
0xd1, 0xaf, 0x46, 0xa4, 0x59, 0x06, 0x03, 0xdb, 0xa2, 0x11, 0x51, 0x3c, 0x0a, 0xfe, 0xce, 0x48,
0x31, 0x93, 0x25, 0xd8, 0xeb, 0x47, 0xa3, 0x70, 0x32, 0xe3, 0x8b, 0x3c, 0x4c, 0x46, 0x1f, 0xbc,
0x68, 0x1d, 0x95, 0xe9, 0x2a, 0xb2, 0xf5, 0xad, 0xf8, 0xac, 0xd6, 0xb7, 0x94, 0x43, 0x90, 0xbd,
0x6e, 0xff, 0x94, 0x87, 0x33, 0x11, 0xed, 0x11, 0xb6, 0x3d, 0x1d, 0x7a, 0x22, 0x3f, 0xb9, 0x00,
0xe5, 0x75, 0x87, 0xad, 0x3f, 0x22, 0x77, 0x94, 0x1e, 0x4b, 0xf9, 0xfd, 0xd3, 0xea, 0x77, 0x20,
0xa2, 0x99, 0xa3, 0x4a, 0x27, 0xe6, 0x82, 0xda, 0x0b, 0x27, 0xbf, 0x9d, 0xa8, 0x3c, 0x02, 0xd3,
0xcc, 0x51, 0xb5, 0x67, 0x6e, 0x41, 0x6d, 0x16, 0x93, 0xdf, 0x9d, 0xe0, 0x12, 0x98, 0x90, 0x4b,
0x6a, 0x77, 0x2d, 0xab, 0x37, 0x2b, 0xfd, 0xa2, 0x6c, 0x9c, 0xa2, 0x99, 0xa3, 0xd9, 0x3d, 0x5d,
0x89, 0xdf, 0x06, 0x90, 0x47, 0xca, 0x4c, 0x6a, 0x03, 0x23, 0xae, 0x99, 0xa3, 0xc9, 0xdf, 0x11,
0xb8, 0x9b, 0x78, 0xd8, 0x2d, 0xcf, 0x91, 0xb3, 0x29, 0x56, 0x8e, 0x6a, 0xe6, 0x68, 0xea, 0x09,
0x78, 0xe2, 0x95, 0xb1, 0x3c, 0x49, 0xd2, 0x83, 0x22, 0x4e, 0x19, 0x54, 0xbc, 0x48, 0xfe, 0xcd,
0xd4, 0x93, 0x5c, 0x59, 0x52, 0x3f, 0x97, 0x62, 0x16, 0xc8, 0x66, 0x8e, 0xa6, 0x1e, 0xf0, 0xce,
0x86, 0x8f, 0x4f, 0x65, 0x57, 0xd2, 0x69, 0x25, 0xd3, 0x61, 0x7f, 0xc6, 0xb5, 0x14, 0x3e, 0x4e,
0x5d, 0x50, 0x1f, 0x1d, 0xca, 0xe7, 0x61, 0x24, 0x35, 0xca, 0x8a, 0x63, 0x71, 0xeb, 0x28, 0xfe,
0xf7, 0xa3, 0xf4, 0x5b, 0x1f, 0xf9, 0xe8, 0xeb, 0x7c, 0x8a, 0x53, 0x62, 0x9b, 0x39, 0x9a, 0x7e,
0x1b, 0x74, 0x37, 0xf1, 0xce, 0x44, 0x46, 0x97, 0x69, 0xad, 0x72, 0x94, 0xa2, 0x55, 0x7c, 0x91,
0xf2, 0x51, 0xfa, 0xe1, 0x83, 0x76, 0x2a, 0x73, 0x68, 0x89, 0x55, 0x86, 0x0e, 0x1f, 0x4a, 0xdc,
0x4d, 0xb4, 0xd6, 0x6b, 0xa7, 0xb3, 0x87, 0x36, 0x03, 0x53, 0x1d, 0x5a, 0x34, 0xe1, 0x27, 0x9a,
0xbc, 0xb5, 0x33, 0x99, 0x06, 0x45, 0x9c, 0x62, 0x50, 0xd1, 0x10, 0x7e, 0x37, 0xd1, 0xcb, 0xa5,
0x4d, 0x25, 0x07, 0x55, 0x50, 0x7c, 0x50, 0xb5, 0xeb, 0x6b, 0x41, 0x6d, 0x71, 0xd2, 0xa6, 0x93,
0x06, 0x8a, 0x31, 0xdc, 0x40, 0x4a, 0x2b, 0x94, 0x8e, 0xed, 0x13, 0x1a, 0x41, 0xf2, 0x7a, 0xf4,
0x85, 0x4b, 0x1b, 0xcd, 0x1c, 0xc5, 0xc6, 0x0a, 0x43, 0x34, 0xe6, 0x68, 0x67, 0x91, 0x62, 0x32,
0xa4, 0xe0, 0xb0, 0x66, 0x8e, 0x8a, 0xa6, 0x9d, 0x3b, 0x4a, 0xed, 0x5e, 0x9b, 0x49, 0xba, 0x88,
0x08, 0xc1, 0x5d, 0x44, 0x5c, 0xe1, 0xbf, 0x37, 0x5e, 0xdf, 0xd6, 0xce, 0x25, 0xcf, 0xba, 0x34,
0xbe, 0x99, 0xa3, 0xe3, 0x35, 0xf1, 0xbb, 0x89, 0x92, 0xaf, 0x76, 0x3e, 0xa9, 0x2e, 0x05, 0xc5,
0xd5, 0xa5, 0x16, 0x87, 0xd7, 0x33, 0x1b, 0x29, 0xb5, 0x0b, 0x28, 0xe0, 0x8d, 0x48, 0xc0, 0x38,
0x49, 0x33, 0x47, 0x33, 0x5b, 0x30, 0x3f, 0xdd, 0xbf, 0xf0, 0xaa, 0x69, 0x28, 0xf5, 0xaa, 0xb2,
0xb9, 0x32, 0xe9, 0x9a, 0x39, 0xba, 0x7f, 0xf1, 0x76, 0x41, 0xad, 0x81, 0x6a, 0x17, 0x93, 0xf6,
0x8d, 0x31, 0xdc, 0xbe, 0x4a, 0xad, 0x74, 0x41, 0x2d, 0x39, 0x6a, 0x97, 0xc6, 0xb9, 0x62, 0xa7,
0xaa, 0x94, 0x26, 0x69, 0x76, 0x85, 0x4f, 0x7b, 0x03, 0xf9, 0x2f, 0x87, 0xfc, 0x59, 0x34, 0xcd,
0x1c, 0xcd, 0xae, 0x0e, 0xd2, 0xec, 0x22, 0x9b, 0x76, 0xf9, 0x20, 0x99, 0xd1, 0xd7, 0x65, 0x17,
0xe8, 0xcc, 0x03, 0xea, 0x5c, 0xda, 0x9b, 0xc9, 0x44, 0xd4, 0xbe, 0x84, 0xcd, 0x1c, 0x3d, 0xa0,
0x5a, 0xf6, 0x70, 0x9f, 0xa2, 0x93, 0x76, 0x25, 0xd9, 0xbb, 0x94, 0x49, 0xd4, 0xcc, 0xd1, 0x7d,
0x4a, 0x56, 0x0f, 0xf7, 0xa9, 0xfc, 0x68, 0xfa, 0x81, 0x62, 0x23, 0x7d, 0xec, 0x53, 0x37, 0x5a,
0xcf, 0x2c, 0xbf, 0x68, 0x57, 0x93, 0xab, 0x3a, 0x83, 0x84, 0xaf, 0xea, 0xac, 0xc2, 0xcd, 0x7a,
0x66, 0xfd, 0x43, 0xbb, 0x76, 0x80, 0xc0, 0xe8, 0x1b, 0x33, 0x2b, 0x27, 0xeb, 0x99, 0x05, 0x08,
0xcd, 0x48, 0x0a, 0xcc, 0x20, 0xe1, 0x02, 0xb3, 0x4a, 0x17, 0xeb, 0x99, 0x15, 0x00, 0xed, 0xfa,
0x01, 0x02, 0xe3, 0x2f, 0xcc, 0xaa, 0x1d, 0xdc, 0x4d, 0xa4, 0xe0, 0xb5, 0xb7, 0x92, 0x2e, 0x45,
0x41, 0x71, 0x97, 0xa2, 0x26, 0xeb, 0x97, 0xc6, 0xb2, 0x96, 0xda, 0xdb, 0xc9, 0x00, 0x20, 0x85,
0x6e, 0xe6, 0xe8, 0x58, 0x9e, 0x73, 0x69, 0x2c, 0xf1, 0xa6, 0xdd, 0xd8, 0x4f, 0x08, 0xa2, 0x93,
0x42, 0x44, 0xaa, 0x6e, 0x35, 0x23, 0x5d, 0xa4, 0xbd, 0x93, 0xbc, 0x09, 0x8e, 0x11, 0x34, 0x73,
0x34, 0x23, 0xc9, 0x44, 0xb3, 0xb3, 0x0d, 0xda, 0x6c, 0x72, 0xdb, 0x66, 0xd1, 0xf0, 0x6d, 0x9b,
0x99, 0xa9, 0x58, 0xcb, 0x8a, 0xd5, 0xb4, 0x9b, 0xc9, 0x3b, 0xdb, 0x38, 0x05, 0xbf, 0xb3, 0x65,
0xc4, 0x78, 0x34, 0x3b, 0x7e, 0xd6, 0x7e, 0xe5, 0xc0, 0x2f, 0x44, 0x9a, 0x8c, 0x2f, 0x14, 0xb1,
0x77, 0x7c, 0xad, 0x7a, 0xd8, 0xef, 0xba, 0xa6, 0xa5, 0xbd, 0x9b, 0x79, 0xad, 0x12, 0x48, 0xe5,
0x5a, 0x25, 0x00, 0xfc, 0x02, 0xa0, 0xc6, 0x32, 0xda, 0xad, 0xe4, 0x05, 0x40, 0xc5, 0xf1, 0x0b,
0x40, 0x22, 0xee, 0x59, 0x1a, 0x8b, 0x20, 0xb4, 0xf7, 0x92, 0x0b, 0x20, 0x85, 0xe6, 0x0b, 0x20,
0x05, 0x6a, 0x4c, 0x40, 0x79, 0x85, 0x13, 0x1b, 0x7f, 0x9a, 0x87, 0xc9, 0xcd, 0xc0, 0x63, 0x66,
0x4f, 0xa6, 0x2c, 0x2e, 0x41, 0x55, 0x7c, 0x64, 0xf8, 0x53, 0x88, 0x34, 0xfa, 0x9f, 0xdc, 0x80,
0xd3, 0x6b, 0xa6, 0x1f, 0x20, 0xa7, 0xf2, 0x33, 0x35, 0x34, 0x05, 0x25, 0x6b, 0x82, 0x4e, 0xf0,
0xe1, 0xcf, 0x05, 0x14, 0x9f, 0xf9, 0x36, 0xa7, 0xca, 0xc3, 0x0c, 0x7c, 0x90, 0x93, 0xe2, 0x35,
0x46, 0x79, 0x18, 0x53, 0xdf, 0x8b, 0x47, 0x4c, 0xeb, 0x63, 0x3f, 0xff, 0x28, 0x03, 0x90, 0xaf,
0x19, 0x85, 0x8f, 0xfd, 0x78, 0xe4, 0x75, 0x28, 0x3e, 0x5c, 0x5d, 0x56, 0x9f, 0xff, 0x24, 0x7f,
0x11, 0x93, 0x63, 0xc9, 0x3b, 0xd1, 0xed, 0xf8, 0x21, 0x5d, 0x93, 0xf9, 0x0a, 0x7c, 0xde, 0x3f,
0xf0, 0xba, 0x54, 0x41, 0x35, 0xa6, 0xbe, 0xfc, 0xaf, 0x2b, 0xb9, 0x2f, 0xbf, 0xba, 0x92, 0xff,
0xd9, 0x57, 0x57, 0xf2, 0xff, 0xf9, 0xd5, 0x95, 0x7c, 0xbb, 0x82, 0xca, 0x7a, 0xff, 0x17, 0x01,
0x00, 0x00, 0xff, 0xff, 0xf7, 0xd0, 0x29, 0x5d, 0x2e, 0x54, 0x00, 0x00,
0x54, 0x2c, 0x8b, 0x11, 0xcd, 0xb5, 0x36, 0x9b, 0x0f, 0x9b, 0x43, 0x52, 0x3b, 0x8c, 0x29, 0x91,
0x2e, 0x52, 0x9b, 0xbd, 0xac, 0x07, 0x3d, 0xd3, 0x25, 0xb2, 0x57, 0x33, 0xdd, 0xb3, 0xdd, 0x3d,
0x94, 0xe8, 0x53, 0x3e, 0x2e, 0x7b, 0x70, 0x80, 0x60, 0x73, 0x08, 0x82, 0x1c, 0x12, 0x20, 0xc8,
0x21, 0x41, 0x90, 0x04, 0x39, 0x04, 0xb9, 0x3b, 0x1b, 0x18, 0x08, 0x36, 0x59, 0x6c, 0xce, 0x99,
0x24, 0x06, 0x72, 0x19, 0x20, 0x7f, 0x20, 0x08, 0x90, 0xa0, 0x5e, 0x55, 0x77, 0x57, 0xf7, 0x34,
0x29, 0x4b, 0xd4, 0x82, 0xa0, 0xc4, 0x1b, 0xf9, 0xbe, 0xba, 0xea, 0xbd, 0xaa, 0x57, 0xf5, 0x3e,
0x6a, 0x60, 0x92, 0xed, 0x32, 0x27, 0xf0, 0x6f, 0xf7, 0x3d, 0x37, 0x70, 0x49, 0x45, 0xfc, 0x77,
0x69, 0x66, 0xdb, 0xdd, 0x76, 0x11, 0x34, 0xc7, 0xff, 0x12, 0xd8, 0x4b, 0xfa, 0xb6, 0xeb, 0x6e,
0x77, 0xd9, 0x1c, 0xfe, 0xd7, 0x1e, 0x3c, 0x9a, 0x0b, 0xec, 0x1e, 0xf3, 0x03, 0xb3, 0xd7, 0x97,
0x04, 0x97, 0xd3, 0x04, 0x7e, 0xe0, 0x0d, 0x3a, 0x81, 0xc0, 0x1a, 0x3f, 0x2c, 0x40, 0xf5, 0x3e,
0x0b, 0x4c, 0xcb, 0x0c, 0x4c, 0x72, 0x19, 0xca, 0xab, 0x8e, 0xc5, 0x9e, 0x6a, 0xf9, 0xab, 0xf9,
0xd9, 0x62, 0xa3, 0x32, 0x1a, 0xea, 0x05, 0x66, 0x53, 0x01, 0x24, 0x6f, 0x42, 0x69, 0x6b, 0xaf,
0xcf, 0xb4, 0xc2, 0xd5, 0xfc, 0x6c, 0xad, 0x51, 0x1b, 0x0d, 0xf5, 0x32, 0x8e, 0x8c, 0x22, 0x98,
0x5c, 0x83, 0xc2, 0xea, 0xb2, 0x56, 0x44, 0xe4, 0xf4, 0x68, 0xa8, 0x9f, 0x1a, 0xd8, 0xd6, 0x2d,
0xb7, 0x67, 0x07, 0xac, 0xd7, 0x0f, 0xf6, 0x68, 0x61, 0x75, 0x99, 0xdc, 0x80, 0xd2, 0x92, 0x6b,
0x31, 0xad, 0x84, 0x44, 0x64, 0x34, 0xd4, 0x4f, 0x77, 0x5c, 0x8b, 0x29, 0x54, 0x88, 0x27, 0x1f,
0x41, 0x69, 0xcb, 0xee, 0x31, 0xad, 0x7c, 0x35, 0x3f, 0x5b, 0x9f, 0xbf, 0x74, 0x5b, 0xcc, 0xe0,
0x76, 0x38, 0x83, 0xdb, 0x5b, 0xe1, 0x14, 0x1b, 0x53, 0x5f, 0x0e, 0xf5, 0xdc, 0x68, 0xa8, 0x97,
0xf8, 0xac, 0x7f, 0xef, 0xdf, 0xf5, 0x3c, 0x45, 0x4e, 0x32, 0x0f, 0xf5, 0xa5, 0xee, 0xc0, 0x0f,
0x98, 0xf7, 0xc0, 0xec, 0x31, 0xad, 0x82, 0x1f, 0x9c, 0x1a, 0x0d, 0xf5, 0xc9, 0x8e, 0x00, 0xb7,
0x1c, 0xb3, 0xc7, 0xa8, 0x4a, 0x64, 0x7c, 0x1f, 0xce, 0x6c, 0x32, 0xdf, 0xb7, 0x5d, 0x27, 0x52,
0xc8, 0xdb, 0x50, 0x93, 0xa0, 0xd5, 0x65, 0x54, 0x4a, 0xad, 0x31, 0x31, 0x1a, 0xea, 0x45, 0xdf,
0xb6, 0x68, 0x8c, 0x21, 0xbf, 0x08, 0x13, 0xbf, 0x61, 0x07, 0x3b, 0xf7, 0xef, 0x2d, 0x4a, 0xe5,
0x9c, 0x1f, 0x0d, 0x75, 0xf2, 0xc4, 0x0e, 0x76, 0x5a, 0xbd, 0x47, 0xa6, 0x32, 0xbd, 0x90, 0xcc,
0xf8, 0x83, 0x3c, 0x4c, 0x3e, 0xf4, 0x99, 0xa7, 0xa8, 0xbe, 0xc4, 0xff, 0x97, 0x1f, 0xa9, 0xf2,
0x29, 0x0d, 0x7c, 0xe6, 0x51, 0x84, 0x92, 0x9b, 0x50, 0x5e, 0x73, 0xb7, 0x6d, 0x47, 0x8a, 0x3f,
0x3b, 0x1a, 0xea, 0x67, 0xba, 0x1c, 0xa0, 0xc8, 0x16, 0x14, 0xe4, 0xd7, 0x60, 0x72, 0xb5, 0xd7,
0x67, 0x9e, 0xef, 0x3a, 0x66, 0xe0, 0x7a, 0xd2, 0x20, 0x97, 0x46, 0x43, 0xfd, 0xbc, 0xad, 0xc0,
0x15, 0xc6, 0x04, 0xbd, 0xf1, 0x37, 0x45, 0x38, 0xbd, 0xc9, 0xbc, 0x5d, 0x65, 0x6c, 0x8b, 0x5c,
0x31, 0x1c, 0xc2, 0xd5, 0xe4, 0xf7, 0xcd, 0x0e, 0x93, 0xc3, 0xbc, 0x30, 0x1a, 0xea, 0x67, 0x9d,
0x10, 0xa8, 0x88, 0x4c, 0xd3, 0x93, 0x9b, 0x50, 0x15, 0xa0, 0xd5, 0x65, 0x39, 0x87, 0x53, 0xa3,
0xa1, 0x5e, 0xf3, 0x11, 0xd6, 0xb2, 0x2d, 0x1a, 0xa1, 0xc9, 0x4a, 0xf8, 0xfd, 0xa6, 0xeb, 0x07,
0x5c, 0xb8, 0x9c, 0xc2, 0x9b, 0xa3, 0xa1, 0x7e, 0x51, 0x32, 0xec, 0x48, 0x94, 0xf2, 0xc9, 0x14,
0x13, 0xf9, 0x25, 0x00, 0x01, 0x59, 0xb4, 0x2c, 0x4f, 0xae, 0xb8, 0x8b, 0xa3, 0xa1, 0x7e, 0x4e,
0x8a, 0x30, 0x2d, 0x4b, 0x55, 0x82, 0x42, 0x4c, 0x7a, 0x30, 0x29, 0xfe, 0x5b, 0x33, 0xdb, 0xac,
0xeb, 0x6b, 0xe5, 0xab, 0xc5, 0xd9, 0xfa, 0xfc, 0xec, 0x6d, 0xb9, 0x2b, 0x93, 0xda, 0xb9, 0xad,
0x92, 0xae, 0x38, 0x81, 0xb7, 0xd7, 0xd0, 0xe5, 0xa2, 0xbc, 0x20, 0x3f, 0xd5, 0x45, 0x9c, 0xaa,
0x71, 0x95, 0xe7, 0xd2, 0x87, 0x30, 0x3d, 0x26, 0x83, 0x4c, 0x41, 0xf1, 0x31, 0xdb, 0x13, 0x7a,
0xa6, 0xfc, 0x4f, 0x32, 0x03, 0xe5, 0x5d, 0xb3, 0x3b, 0x90, 0xfb, 0x8f, 0x8a, 0x7f, 0xbe, 0x55,
0xf8, 0x66, 0xde, 0xf8, 0xfb, 0x3c, 0x90, 0x25, 0xd7, 0x71, 0x58, 0x27, 0x50, 0x17, 0xef, 0x07,
0x50, 0x5b, 0x73, 0x3b, 0x66, 0x17, 0x15, 0x20, 0x0c, 0xa6, 0x8d, 0x86, 0xfa, 0x0c, 0x9f, 0xf9,
0xed, 0x2e, 0xc7, 0x28, 0x43, 0x8a, 0x49, 0xb9, 0xe6, 0x28, 0xeb, 0xb9, 0x01, 0x43, 0xc6, 0x42,
0xac, 0x39, 0x64, 0xf4, 0x10, 0xa5, 0x6a, 0x2e, 0x26, 0x26, 0x73, 0x50, 0xdd, 0xe0, 0x9b, 0xb4,
0xe3, 0x76, 0xa5, 0xd5, 0x70, 0xa9, 0xe2, 0xc6, 0x55, 0x58, 0x22, 0x22, 0xe3, 0xb7, 0x0b, 0x70,
0xf1, 0xe3, 0x41, 0x9b, 0x79, 0x0e, 0x0b, 0x98, 0x2f, 0x77, 0x63, 0x34, 0x83, 0x07, 0x30, 0x3d,
0x86, 0x94, 0x33, 0xb9, 0x3a, 0x1a, 0xea, 0x97, 0x1f, 0x47, 0xc8, 0x96, 0xdc, 0xd6, 0xca, 0x47,
0xc6, 0x59, 0x49, 0x13, 0xce, 0xc4, 0x40, 0xbe, 0xb1, 0x7c, 0xad, 0x70, 0xb5, 0x38, 0x5b, 0x6b,
0x5c, 0x19, 0x0d, 0xf5, 0x4b, 0x8a, 0x34, 0xbe, 0xf5, 0x54, 0x83, 0xa5, 0xd9, 0xc8, 0xc7, 0x30,
0x15, 0x83, 0xbe, 0xed, 0xb9, 0x83, 0xbe, 0xaf, 0x15, 0x51, 0x94, 0x3e, 0x1a, 0xea, 0x6f, 0x28,
0xa2, 0xb6, 0x11, 0xa9, 0xc8, 0x1a, 0x63, 0x34, 0xfe, 0xab, 0x08, 0xe7, 0x62, 0xe0, 0x86, 0x6b,
0x45, 0x0a, 0x58, 0x57, 0x15, 0xb0, 0xe1, 0x5a, 0xe8, 0xcc, 0x84, 0x02, 0xae, 0x8d, 0x86, 0xfa,
0x9b, 0xca, 0x77, 0xfa, 0xae, 0xd5, 0x4a, 0x6d, 0x89, 0x71, 0x5e, 0xf2, 0x29, 0x9c, 0x1f, 0x03,
0x8a, 0x1d, 0x2d, 0xec, 0x7c, 0x63, 0x34, 0xd4, 0x8d, 0x0c, 0xa9, 0xe9, 0x0d, 0xbe, 0x8f, 0x14,
0x62, 0xc2, 0x05, 0x45, 0xed, 0xae, 0x13, 0x98, 0xb6, 0x23, 0x7d, 0xb0, 0x58, 0x0f, 0xef, 0x8c,
0x86, 0xfa, 0x75, 0xd5, 0x6e, 0x21, 0x4d, 0x7a, 0xf0, 0xfb, 0xc9, 0x21, 0x16, 0x68, 0x19, 0xa8,
0xd5, 0x9e, 0xb9, 0x1d, 0x1e, 0x2c, 0xb3, 0xa3, 0xa1, 0xfe, 0x56, 0xe6, 0x37, 0x6c, 0x4e, 0xa5,
0x7c, 0x64, 0x5f, 0x49, 0x84, 0x02, 0x89, 0x71, 0x0f, 0x5c, 0x8b, 0xe1, 0x1c, 0xca, 0x28, 0xdf,
0x18, 0x0d, 0xf5, 0x2b, 0x8a, 0x7c, 0xc7, 0xb5, 0x58, 0x7a, 0xf8, 0x19, 0xdc, 0xc6, 0xff, 0x96,
0xb8, 0x63, 0xc1, 0x43, 0x63, 0x33, 0x30, 0xbd, 0x80, 0x7c, 0x2b, 0x3e, 0x7b, 0xd1, 0xaa, 0xf5,
0xf9, 0xa9, 0xd0, 0xc9, 0x84, 0xf0, 0xc6, 0x24, 0x77, 0x26, 0x3f, 0x1d, 0xea, 0xf9, 0xd1, 0x50,
0xcf, 0xd1, 0xaa, 0xb2, 0xbb, 0xc5, 0x81, 0x51, 0x40, 0xbe, 0x99, 0x90, 0x4f, 0x3d, 0x54, 0x52,
0xbc, 0xe2, 0x28, 0xf9, 0x10, 0x26, 0xe4, 0x18, 0xd0, 0x22, 0xf5, 0xf9, 0x0b, 0xb1, 0x5f, 0x4b,
0x1c, 0x7e, 0x29, 0xee, 0x90, 0x8b, 0xfc, 0x0a, 0x54, 0x84, 0xbb, 0x42, 0x6d, 0xd7, 0xe7, 0xcf,
0x67, 0xfb, 0xc5, 0x14, 0xbb, 0xe4, 0x21, 0x4d, 0x80, 0xd8, 0x55, 0x45, 0x07, 0xbc, 0x94, 0x30,
0xee, 0xc4, 0x52, 0x52, 0x14, 0x5e, 0xf2, 0x01, 0x4c, 0x6e, 0x31, 0xaf, 0x67, 0x3b, 0x66, 0x77,
0xd3, 0xfe, 0x2c, 0x3c, 0xe3, 0xf1, 0x52, 0xe1, 0xdb, 0x9f, 0xa9, 0xb6, 0x48, 0xd0, 0x91, 0xef,
0x65, 0x39, 0x95, 0x09, 0x1c, 0xc8, 0xb5, 0x70, 0x20, 0xfb, 0xba, 0xa4, 0xd4, 0x78, 0x32, 0x7c,
0xcc, 0x27, 0x70, 0x2a, 0xb1, 0x37, 0xb4, 0x2a, 0x8a, 0x7e, 0x73, 0x5c, 0xb4, 0xb2, 0xd1, 0x53,
0x62, 0x93, 0x12, 0xf8, 0x89, 0xb8, 0xea, 0xd8, 0x81, 0x6d, 0x76, 0x97, 0xdc, 0x5e, 0xcf, 0x74,
0x2c, 0xad, 0x86, 0xae, 0x06, 0x4f, 0x44, 0x5b, 0x60, 0x5a, 0x1d, 0x81, 0x52, 0x4f, 0xc4, 0x24,
0x93, 0xf1, 0xe7, 0x45, 0xa8, 0x4b, 0x23, 0xfe, 0xba, 0x6b, 0x3b, 0x27, 0xab, 0xef, 0x30, 0xab,
0x2f, 0x73, 0x15, 0x55, 0x5e, 0xd6, 0x2a, 0x32, 0x3e, 0x2f, 0x44, 0xae, 0x62, 0xc3, 0xb3, 0x9d,
0xc3, 0xb9, 0x8a, 0x1b, 0x00, 0x4b, 0x3b, 0x03, 0xe7, 0xb1, 0xb8, 0xdb, 0x17, 0xe2, 0xbb, 0x7d,
0xc7, 0xa6, 0x0a, 0x86, 0x5f, 0xf0, 0x97, 0xb9, 0x7c, 0x6e, 0x99, 0xc9, 0x46, 0xed, 0x4b, 0x21,
0x29, 0xff, 0x1e, 0x45, 0x30, 0xd1, 0xa1, 0xdc, 0xd8, 0x0b, 0x98, 0x8f, 0x9a, 0x2f, 0x8a, 0x00,
0xa0, 0xcd, 0x01, 0x54, 0xc0, 0xc9, 0x02, 0x4c, 0x2f, 0xb3, 0xae, 0xb9, 0x77, 0xdf, 0xee, 0x76,
0x6d, 0x9f, 0x75, 0x5c, 0xc7, 0xf2, 0x51, 0xc9, 0xf2, 0x73, 0x3d, 0x9f, 0x8e, 0x13, 0x10, 0x03,
0x2a, 0xeb, 0x8f, 0x1e, 0xf9, 0x2c, 0x40, 0xf5, 0x15, 0x1b, 0x30, 0x1a, 0xea, 0x15, 0x17, 0x21,
0x54, 0x62, 0x8c, 0x9f, 0x15, 0xe0, 0x94, 0x54, 0x07, 0x65, 0xdf, 0x67, 0x9d, 0xa3, 0x71, 0x9d,
0xf1, 0xda, 0x2b, 0x1e, 0x7a, 0xed, 0x95, 0x0e, 0xb1, 0xf6, 0x0c, 0xa8, 0x50, 0x66, 0xfa, 0x72,
0x05, 0xd7, 0x84, 0xc6, 0x3c, 0x84, 0x50, 0x89, 0x21, 0xd7, 0x60, 0xe2, 0xbe, 0xf9, 0xd4, 0xee,
0x0d, 0x7a, 0x52, 0xad, 0x18, 0xb7, 0xf4, 0xcc, 0xa7, 0x34, 0x84, 0x1b, 0x7f, 0x5d, 0xe2, 0x72,
0xb8, 0xaf, 0x3c, 0x9e, 0xae, 0xe0, 0xe5, 0x29, 0x34, 0x36, 0x6c, 0xf9, 0x05, 0x0c, 0xfb, 0xda,
0x1c, 0x44, 0xc6, 0x5f, 0x4c, 0xf0, 0xa0, 0x0a, 0xb5, 0xbf, 0xe2, 0x58, 0x27, 0xab, 0xe6, 0x30,
0xab, 0x66, 0x19, 0xa6, 0x57, 0x9c, 0x1d, 0xd3, 0xe9, 0x30, 0x8b, 0xb2, 0x8e, 0xeb, 0x59, 0xb6,
0xb3, 0x8d, 0x4b, 0xa7, 0x2a, 0xb2, 0x07, 0x4c, 0x22, 0x5b, 0x5e, 0x88, 0xa5, 0xe3, 0x0c, 0xe4,
0x0e, 0xd4, 0x57, 0x9d, 0x80, 0x79, 0x66, 0x27, 0xb0, 0x77, 0x19, 0xae, 0x9e, 0x6a, 0xe3, 0xcc,
0x68, 0xa8, 0xd7, 0xed, 0x18, 0x4c, 0x55, 0x1a, 0xb2, 0x00, 0x93, 0x1b, 0xa6, 0x17, 0xd8, 0x1d,
0xbb, 0x6f, 0x3a, 0x81, 0xaf, 0x55, 0xf1, 0x2e, 0x81, 0xb9, 0x91, 0xbe, 0x02, 0xa7, 0x09, 0x2a,
0xf2, 0x3d, 0xa8, 0xe1, 0x9d, 0x15, 0xf3, 0x32, 0xb5, 0x67, 0xe6, 0x65, 0xae, 0xc7, 0x21, 0x30,
0xaa, 0xbd, 0xe5, 0x73, 0xe6, 0x78, 0x2b, 0x60, 0xaa, 0x26, 0x96, 0x48, 0xbe, 0x0b, 0x13, 0x2b,
0x8e, 0x85, 0xc2, 0xe1, 0x99, 0xc2, 0x0d, 0x29, 0xfc, 0x7c, 0x2c, 0xdc, 0xed, 0xa7, 0x64, 0x87,
0xe2, 0xb2, 0x77, 0x59, 0xfd, 0xe7, 0xb7, 0xcb, 0x26, 0x7f, 0x0e, 0xd7, 0xbd, 0x53, 0x2f, 0x72,
0xdd, 0xfb, 0x0c, 0xea, 0x8d, 0x8d, 0x7b, 0xd1, 0x86, 0xbb, 0x08, 0xc5, 0x0d, 0x99, 0xc4, 0x2a,
0x89, 0xc3, 0xa0, 0x6f, 0x5b, 0x94, 0xc3, 0xc8, 0x4d, 0xa8, 0x2e, 0x61, 0x98, 0x2a, 0x93, 0x33,
0x25, 0x91, 0x9c, 0xe9, 0x20, 0x0c, 0x93, 0x33, 0x21, 0x9a, 0xbc, 0x0d, 0x13, 0x1b, 0x9e, 0xbb,
0xed, 0x99, 0x3d, 0x19, 0xcf, 0xd5, 0x47, 0x43, 0x7d, 0xa2, 0x2f, 0x40, 0x34, 0xc4, 0x19, 0xbf,
0x9f, 0x87, 0xca, 0x66, 0x60, 0x06, 0x03, 0x9f, 0x73, 0x6c, 0x0e, 0x3a, 0x1d, 0xe6, 0xfb, 0xf8,
0xed, 0xaa, 0xe0, 0xf0, 0x05, 0x88, 0x86, 0x38, 0x72, 0x13, 0xca, 0x2b, 0x9e, 0xe7, 0x7a, 0x6a,
0x86, 0x8b, 0x71, 0x80, 0x9a, 0xe1, 0x42, 0x0a, 0x72, 0x17, 0xea, 0xc2, 0x4d, 0xf8, 0x3e, 0x8f,
0xf9, 0xc4, 0x38, 0xce, 0x8d, 0x86, 0xfa, 0x74, 0x4f, 0x80, 0x14, 0x16, 0x95, 0xd2, 0xf8, 0x02,
0x53, 0x5b, 0xb8, 0x64, 0xa4, 0x92, 0x5e, 0xc7, 0x3b, 0xf0, 0xfb, 0x50, 0x6c, 0x6c, 0xdc, 0x93,
0x3e, 0xeb, 0x6c, 0xc8, 0xaa, 0x2c, 0x95, 0x14, 0x1f, 0xa7, 0x26, 0x97, 0xa1, 0xb4, 0xc1, 0x97,
0x4f, 0x05, 0x97, 0x07, 0xa6, 0x27, 0xfb, 0x7c, 0xfd, 0x20, 0x14, 0xb1, 0x66, 0xb0, 0x83, 0xee,
0x47, 0x26, 0x2f, 0xfb, 0x66, 0xb0, 0x43, 0x11, 0xca, 0xb1, 0x8b, 0xde, 0xf6, 0xae, 0x74, 0x34,
0x88, 0x35, 0xbd, 0xed, 0x5d, 0x8a, 0x50, 0x32, 0x07, 0x40, 0x59, 0x30, 0xf0, 0x1c, 0xcc, 0x0c,
0x73, 0xcf, 0x52, 0x16, 0x0e, 0xcc, 0x43, 0x68, 0xab, 0xe3, 0x5a, 0x8c, 0x2a, 0x24, 0xc6, 0x9f,
0xc5, 0x61, 0xcc, 0xb2, 0xed, 0x3f, 0x3e, 0x31, 0xe1, 0x73, 0x98, 0x90, 0x1b, 0xa9, 0x92, 0x69,
0x24, 0x1d, 0xca, 0xf7, 0xba, 0xe6, 0xb6, 0x8f, 0x36, 0x2c, 0x8b, 0xcb, 0xfd, 0x23, 0x0e, 0xa0,
0x02, 0x9e, 0xb2, 0x53, 0xf5, 0xd9, 0x76, 0xfa, 0xb7, 0x78, 0xb7, 0x3d, 0x60, 0xc1, 0x13, 0xd7,
0x3b, 0x31, 0xd5, 0xd7, 0x35, 0xd5, 0x0d, 0x98, 0xd8, 0xf4, 0x3a, 0x98, 0x7e, 0x15, 0xd6, 0x9a,
0x1c, 0x0d, 0xf5, 0xaa, 0xef, 0x75, 0x30, 0x6b, 0x4d, 0x43, 0x24, 0xa7, 0x5b, 0xf6, 0x03, 0xa4,
0x9b, 0x88, 0xe9, 0x2c, 0x3f, 0x90, 0x74, 0x12, 0x29, 0xe9, 0x36, 0x5c, 0x2f, 0x90, 0x86, 0x8b,
0xe8, 0xfa, 0xae, 0x17, 0xd0, 0x10, 0x49, 0xde, 0x05, 0xd8, 0x5a, 0xda, 0xf8, 0x0e, 0xf3, 0x50,
0x5d, 0x62, 0x2f, 0xa2, 0xbb, 0xde, 0x15, 0x20, 0xaa, 0xa0, 0x8d, 0xbf, 0x54, 0xf6, 0x21, 0x37,
0xd0, 0x49, 0x3a, 0xe1, 0x10, 0x77, 0xc9, 0x79, 0x98, 0xc2, 0x18, 0x7a, 0xcb, 0x33, 0x1d, 0xbf,
0x67, 0x07, 0x01, 0xb3, 0xa4, 0xaf, 0xc5, 0xc8, 0x39, 0x78, 0x4a, 0xc7, 0xf0, 0xe4, 0x16, 0x9c,
0x42, 0x18, 0x65, 0x1d, 0x66, 0xef, 0x32, 0x0b, 0xd7, 0x80, 0x64, 0xf0, 0x9e, 0xd2, 0x24, 0xd2,
0xf8, 0xa7, 0x38, 0xa3, 0xb0, 0xc6, 0xcc, 0x5d, 0x76, 0x62, 0xaf, 0x43, 0xd8, 0xcb, 0xf8, 0xd3,
0x22, 0xd4, 0xf8, 0x8c, 0x44, 0xcd, 0xed, 0x28, 0x54, 0xb9, 0x10, 0xde, 0xb0, 0xa4, 0x26, 0x4f,
0x47, 0x9a, 0x40, 0xe8, 0x98, 0x06, 0xc4, 0x6d, 0xec, 0x16, 0x54, 0xee, 0xb3, 0x60, 0xc7, 0xb5,
0x64, 0xaa, 0x7c, 0x66, 0x34, 0xd4, 0xa7, 0x7a, 0x08, 0x51, 0x6e, 0x4d, 0x92, 0x86, 0x3c, 0x06,
0xb2, 0x6a, 0x31, 0x27, 0xb0, 0x83, 0xbd, 0xc5, 0x20, 0xf0, 0xec, 0xf6, 0x20, 0x60, 0xbe, 0xd4,
0xdb, 0x85, 0xb1, 0x0b, 0xfa, 0x26, 0xd6, 0x95, 0x31, 0x3b, 0x3e, 0x63, 0x46, 0xe4, 0xb1, 0xd8,
0xff, 0x19, 0xea, 0x15, 0x41, 0x43, 0x33, 0xc4, 0x92, 0x4f, 0xa0, 0x76, 0xff, 0xde, 0xe2, 0x32,
0xdb, 0xb5, 0x3b, 0x4c, 0x66, 0xd2, 0x2e, 0x46, 0x5a, 0x0c, 0x11, 0x91, 0x4a, 0xb0, 0x92, 0xd5,
0x7b, 0x64, 0xb6, 0x2c, 0x84, 0xab, 0x95, 0xac, 0x88, 0xd8, 0xf8, 0x2a, 0x0f, 0x53, 0x94, 0xf9,
0xee, 0xc0, 0x8b, 0x39, 0xc9, 0x0d, 0x28, 0x29, 0x65, 0x14, 0x0c, 0xd3, 0x53, 0xb9, 0x7b, 0xc4,
0x93, 0x55, 0x98, 0x58, 0x79, 0xda, 0xb7, 0x3d, 0xe6, 0x4b, 0xdb, 0x1c, 0x14, 0x92, 0x9c, 0x95,
0x21, 0xc9, 0x04, 0x13, 0x2c, 0x32, 0x06, 0x11, 0xff, 0x90, 0x0f, 0xa0, 0xf6, 0xb0, 0x6f, 0x99,
0x01, 0xb3, 0x1a, 0x7b, 0xf2, 0xbe, 0x8a, 0xe3, 0x1f, 0x08, 0x60, 0xab, 0xbd, 0xa7, 0x8e, 0x3f,
0x22, 0x25, 0xd7, 0xa1, 0xb8, 0xb5, 0xb5, 0x26, 0x4d, 0x85, 0x35, 0xf5, 0x20, 0x50, 0x8b, 0x76,
0x1c, 0x6b, 0xfc, 0xa8, 0x00, 0xc0, 0x57, 0xc4, 0x92, 0xc7, 0xcc, 0xe0, 0x68, 0xb6, 0x75, 0x03,
0xaa, 0xa1, 0x9a, 0xe5, 0x6a, 0xd4, 0x42, 0xde, 0xb4, 0xfa, 0xd3, 0xdf, 0x0e, 0xf1, 0xfc, 0x02,
0x42, 0xdd, 0x2e, 0x66, 0x17, 0x8b, 0x61, 0x7b, 0x81, 0xc7, 0x01, 0x54, 0xc0, 0xc9, 0xbb, 0x50,
0x93, 0x1b, 0xd0, 0xf5, 0x64, 0xe2, 0x4b, 0x84, 0x29, 0x21, 0x90, 0xc6, 0x78, 0xe3, 0x1f, 0xf2,
0x42, 0x29, 0xcb, 0xac, 0xcb, 0x8e, 0xaf, 0x52, 0x8c, 0x1f, 0xe6, 0x81, 0x70, 0x61, 0x1b, 0xa6,
0xef, 0x3f, 0x71, 0x3d, 0x6b, 0x69, 0xc7, 0x74, 0xb6, 0x8f, 0x64, 0x3a, 0xc6, 0x8f, 0x4a, 0x70,
0x76, 0x51, 0x04, 0x6d, 0xec, 0x07, 0x03, 0xe6, 0x07, 0xc7, 0x7c, 0xbd, 0xdd, 0x4c, 0xae, 0x37,
0x0c, 0x38, 0x71, 0xbd, 0xa9, 0x01, 0xa7, 0x58, 0x79, 0x6f, 0x41, 0x4d, 0xce, 0x79, 0x75, 0x59,
0xae, 0x3c, 0x3c, 0x64, 0x6d, 0x8b, 0xc6, 0x08, 0xf2, 0x1e, 0x4c, 0xca, 0x7f, 0xb8, 0xaf, 0x0d,
0xd3, 0x80, 0xb8, 0x8e, 0x7d, 0x0e, 0xa0, 0x09, 0x34, 0xf9, 0x06, 0xd4, 0xf8, 0xe2, 0xdc, 0xc6,
0x26, 0x8d, 0x89, 0xb8, 0x9d, 0xc2, 0x0a, 0x81, 0xaa, 0x4b, 0x88, 0x28, 0xb9, 0x03, 0x97, 0xb9,
0xdf, 0x6a, 0xec, 0xc0, 0x45, 0xee, 0x57, 0x75, 0xe0, 0x32, 0x0b, 0xfc, 0x29, 0xd4, 0x17, 0x1d,
0xc7, 0x0d, 0x4c, 0x7e, 0x68, 0xf9, 0x32, 0x6f, 0xb3, 0xaf, 0xe7, 0xbe, 0x8e, 0x45, 0xfe, 0x98,
0x3e, 0xd3, 0x75, 0xab, 0x02, 0x8d, 0x3f, 0x2e, 0x40, 0x9d, 0xdf, 0x1c, 0xef, 0xb9, 0xde, 0x13,
0xd3, 0x3b, 0x9a, 0x70, 0x3a, 0x79, 0xa8, 0x17, 0x0f, 0x71, 0x09, 0x8b, 0x8f, 0xd4, 0xd2, 0x73,
0x1c, 0xa9, 0x3c, 0xbc, 0xe5, 0x37, 0xf0, 0x72, 0x1c, 0x57, 0xe1, 0xed, 0x1b, 0xa1, 0xc6, 0x6f,
0x16, 0x00, 0xbe, 0x7b, 0xe7, 0xce, 0x6b, 0xac, 0x20, 0xe3, 0x8f, 0xf2, 0x70, 0x46, 0xe6, 0x5b,
0x94, 0xc6, 0xaa, 0x89, 0x30, 0xb9, 0x95, 0x8f, 0xf3, 0x48, 0x32, 0xa9, 0x45, 0x43, 0x1c, 0x99,
0x87, 0xea, 0xca, 0x53, 0x3b, 0xc0, 0x90, 0x53, 0xe9, 0xac, 0x62, 0x12, 0xa6, 0xb6, 0x94, 0x84,
0x74, 0xe4, 0xbd, 0x30, 0x93, 0x54, 0x8c, 0x37, 0x15, 0x67, 0x58, 0xc9, 0xcc, 0x26, 0x19, 0x7f,
0x57, 0x82, 0xd2, 0xca, 0x53, 0xd6, 0x39, 0xe6, 0xa6, 0x51, 0x6e, 0xd6, 0xa5, 0x43, 0xde, 0xac,
0x5f, 0x24, 0x9b, 0xfd, 0x61, 0x6c, 0xcf, 0x4a, 0xf2, 0xf3, 0x29, 0xcb, 0xa7, 0x3f, 0x1f, 0x5a,
0xfa, 0xf8, 0x15, 0x43, 0x7e, 0x5c, 0x84, 0xe2, 0xe6, 0xd2, 0xc6, 0xc9, 0xba, 0x39, 0xd2, 0x75,
0x73, 0x70, 0xea, 0xd1, 0x80, 0xca, 0xa2, 0xd0, 0x51, 0x35, 0xae, 0x94, 0x9a, 0x08, 0xa1, 0x12,
0x63, 0x7c, 0x5e, 0x80, 0xda, 0xe6, 0xa0, 0xed, 0xef, 0xf9, 0x01, 0xeb, 0x1d, 0x73, 0x6b, 0x5e,
0x96, 0xb1, 0x4d, 0x29, 0xd6, 0x06, 0xf6, 0xb9, 0x8a, 0x88, 0xe6, 0x7a, 0xe8, 0x19, 0x95, 0xdb,
0x73, 0xe4, 0x19, 0x43, 0x7f, 0xf8, 0xb7, 0x05, 0x98, 0x5a, 0xea, 0xda, 0xcc, 0x09, 0x96, 0x6d,
0x5f, 0xde, 0xad, 0x8f, 0xb9, 0x56, 0x0e, 0x97, 0x34, 0xf8, 0x1a, 0xd5, 0x76, 0xe3, 0xb7, 0x0a,
0x50, 0x5f, 0x1c, 0x04, 0x3b, 0x8b, 0x01, 0x1e, 0x2e, 0xaf, 0xe5, 0x31, 0xff, 0xb3, 0x3c, 0x68,
0x94, 0xf9, 0x2c, 0x08, 0x83, 0x95, 0x2d, 0xf7, 0x31, 0x73, 0x5e, 0x42, 0x94, 0xa0, 0xde, 0xf6,
0x0b, 0x2f, 0x78, 0xdb, 0x0f, 0x95, 0x5a, 0x7c, 0xce, 0xa8, 0x87, 0xc7, 0x91, 0x3c, 0x08, 0x78,
0x45, 0xa6, 0xf1, 0x12, 0xc2, 0xe1, 0xa3, 0x9c, 0xc6, 0x3f, 0xe7, 0x61, 0x66, 0xcb, 0xe3, 0x27,
0xba, 0x25, 0x0f, 0xf6, 0x63, 0x6e, 0x97, 0xf1, 0x09, 0x1d, 0x73, 0x0b, 0xfd, 0x6b, 0x1e, 0x2e,
0x26, 0x27, 0xf4, 0x2a, 0x78, 0x81, 0x7f, 0xc9, 0xc3, 0xb9, 0x6f, 0xdb, 0xc1, 0xce, 0xa0, 0x1d,
0x65, 0x98, 0x5e, 0xbd, 0x19, 0x1d, 0xf3, 0x95, 0xf7, 0x93, 0x3c, 0x9c, 0x5d, 0x5f, 0x5d, 0x5e,
0x7a, 0x55, 0x2c, 0x34, 0x36, 0x9f, 0x57, 0xc0, 0x3e, 0x9b, 0x8b, 0xf7, 0xd7, 0x5e, 0x25, 0xfb,
0x24, 0xe6, 0x73, 0xcc, 0xed, 0xf3, 0x3b, 0x15, 0xa8, 0xf3, 0x00, 0x57, 0x26, 0x29, 0x5f, 0xeb,
0x2b, 0xff, 0x3c, 0xd4, 0xa5, 0x1a, 0x30, 0xb6, 0x2c, 0xc7, 0xaf, 0x07, 0x3d, 0x01, 0x6e, 0x61,
0x8c, 0xa9, 0x12, 0xf1, 0xd0, 0xeb, 0x3b, 0xcc, 0x6b, 0xab, 0xed, 0x15, 0xbb, 0xcc, 0x6b, 0x53,
0x84, 0x92, 0xb5, 0xb8, 0x10, 0xb5, 0xb8, 0xb1, 0x8a, 0xef, 0x7e, 0x64, 0xc8, 0x8a, 0x0f, 0x99,
0x3c, 0x89, 0x6b, 0x99, 0x7d, 0x5b, 0xbc, 0x18, 0x52, 0x1f, 0x0c, 0xa5, 0x39, 0xc9, 0x03, 0x98,
0x0e, 0x61, 0xf1, 0x03, 0x9e, 0x6a, 0x86, 0xb8, 0xac, 0xa7, 0x3b, 0xe3, 0xac, 0xe4, 0x43, 0x98,
0x0c, 0x81, 0x1f, 0xdb, 0xf8, 0xbc, 0x80, 0x8b, 0x7a, 0x63, 0x34, 0xd4, 0x2f, 0x44, 0xa2, 0x1e,
0xdb, 0x89, 0x6e, 0xb3, 0x04, 0x83, 0x2a, 0x00, 0xe3, 0x4f, 0xc8, 0x10, 0x90, 0x2a, 0xb2, 0x25,
0x18, 0xc8, 0x37, 0x50, 0x40, 0xdf, 0x75, 0x7c, 0x86, 0xc9, 0xbe, 0x3a, 0xf6, 0x1e, 0x60, 0xc9,
0xcb, 0x93, 0x70, 0xd1, 0x61, 0x92, 0x20, 0x23, 0xeb, 0x00, 0x71, 0x52, 0x46, 0xb6, 0xde, 0x3d,
0x77, 0xba, 0x48, 0x11, 0x61, 0xfc, 0x5f, 0x01, 0xce, 0x2c, 0xf6, 0xfb, 0x27, 0xaf, 0x74, 0x5e,
0x56, 0x63, 0xc3, 0x1c, 0xc0, 0xc6, 0xa0, 0xdd, 0xb5, 0x3b, 0x4a, 0x97, 0x0a, 0xb6, 0x0d, 0xf5,
0x11, 0x2a, 0x1a, 0x55, 0x14, 0x12, 0xe3, 0xf3, 0xa2, 0x6a, 0x01, 0x7c, 0x9d, 0x70, 0x62, 0x81,
0xf2, 0xa1, 0x5c, 0xe1, 0x69, 0x55, 0x99, 0xb2, 0x89, 0x4f, 0x56, 0x8e, 0xc2, 0x0e, 0xda, 0x0e,
0x47, 0xb5, 0x6c, 0x8b, 0xa6, 0x68, 0x8d, 0xff, 0xce, 0xc3, 0x74, 0x6c, 0x8e, 0x97, 0x71, 0x38,
0xcc, 0x01, 0x88, 0x8c, 0x41, 0x94, 0xd5, 0x3f, 0x25, 0x56, 0x84, 0x8f, 0x50, 0xd9, 0x48, 0x16,
0x93, 0x44, 0x29, 0xbe, 0x62, 0x66, 0x8a, 0xef, 0x26, 0x54, 0xa9, 0xf9, 0xe4, 0x93, 0x01, 0xf3,
0xf6, 0x64, 0xda, 0x0b, 0xf3, 0x5a, 0x9e, 0xf9, 0xa4, 0xf5, 0x03, 0x0e, 0xa4, 0x11, 0x9a, 0x18,
0x51, 0xf3, 0x83, 0x92, 0xc9, 0x11, 0xcd, 0x0f, 0x61, 0xcb, 0x83, 0xf1, 0x87, 0x05, 0x98, 0x5a,
0x36, 0x03, 0xb3, 0x6d, 0xfa, 0x71, 0xcb, 0xc0, 0x37, 0xe1, 0x4c, 0x08, 0xe3, 0xe6, 0xb1, 0xa3,
0x07, 0xd0, 0xa7, 0x47, 0x43, 0x1d, 0xac, 0x76, 0xcb, 0x17, 0x50, 0x9a, 0x26, 0x23, 0xbf, 0x1c,
0x4b, 0x8b, 0x1e, 0xc6, 0x16, 0xe2, 0x4d, 0x60, 0xb5, 0x5b, 0x7d, 0x09, 0xa6, 0x63, 0x84, 0xe4,
0x16, 0xd4, 0x43, 0xd8, 0x43, 0xba, 0x2a, 0xe7, 0x8f, 0x83, 0xb6, 0xda, 0xad, 0x81, 0x67, 0x53,
0x15, 0x4d, 0xe6, 0x60, 0x32, 0xfc, 0x57, 0xc9, 0x01, 0x62, 0x5d, 0xc5, 0x6a, 0x8b, 0xe7, 0xee,
0x09, 0x02, 0x95, 0x01, 0x77, 0x48, 0x39, 0xc1, 0x80, 0xaf, 0xcf, 0x13, 0x04, 0xc6, 0x4f, 0x8a,
0x30, 0x13, 0x4f, 0xf0, 0xc4, 0x43, 0xbe, 0x9c, 0xfd, 0x19, 0x67, 0xdb, 0x2a, 0xcf, 0x51, 0x75,
0x6c, 0x40, 0x35, 0x34, 0x85, 0x2c, 0x93, 0x44, 0x57, 0xc5, 0xf4, 0xf2, 0x4d, 0xab, 0x3e, 0xc4,
0x1b, 0x5f, 0x14, 0xc6, 0xec, 0x29, 0x36, 0xca, 0xb1, 0xb4, 0xa7, 0xaa, 0x91, 0xd2, 0x8b, 0x69,
0x84, 0xcc, 0xc3, 0xa9, 0xf0, 0x6f, 0xe1, 0x51, 0xca, 0x4a, 0x5b, 0x65, 0x5b, 0x3a, 0x94, 0x24,
0x89, 0xf1, 0xbb, 0x05, 0x20, 0x29, 0x2d, 0x1e, 0xdb, 0xc7, 0x31, 0x2f, 0x41, 0x87, 0xc6, 0x5f,
0xe5, 0x61, 0x7a, 0xac, 0x5f, 0x8b, 0xbc, 0x0f, 0x20, 0x20, 0x4a, 0xef, 0x15, 0xf6, 0x5c, 0xc4,
0x3d, 0x5c, 0xc2, 0x47, 0x29, 0x64, 0x64, 0x0e, 0xaa, 0xe2, 0xbf, 0xe8, 0x57, 0x23, 0xd2, 0x2c,
0x83, 0x81, 0x6d, 0xd1, 0x88, 0x28, 0xfe, 0x0a, 0xfe, 0x50, 0x49, 0x31, 0x93, 0x25, 0xd8, 0xeb,
0x47, 0x5f, 0xe1, 0x64, 0xc6, 0x17, 0x79, 0x98, 0x8c, 0x06, 0xbc, 0x68, 0x1d, 0x95, 0xe9, 0x2a,
0xb2, 0xf5, 0xad, 0xf8, 0xac, 0xd6, 0xb7, 0x94, 0x43, 0x90, 0xbd, 0x6e, 0xff, 0x98, 0x87, 0x33,
0x11, 0xed, 0x11, 0xb6, 0x3d, 0x1d, 0x7a, 0x22, 0x3f, 0xbe, 0x00, 0xe5, 0x75, 0x87, 0xad, 0x3f,
0x22, 0x77, 0x94, 0x1e, 0x4b, 0x39, 0xfe, 0x69, 0x75, 0x1c, 0x88, 0x68, 0xe6, 0xa8, 0xd2, 0x89,
0xb9, 0xa0, 0xf6, 0xc2, 0xc9, 0xb1, 0x13, 0x95, 0x47, 0x60, 0x9a, 0x39, 0xaa, 0xf6, 0xcc, 0x2d,
0xa8, 0xcd, 0x62, 0x72, 0xdc, 0x09, 0x2e, 0x81, 0x09, 0xb9, 0xa4, 0x76, 0xd7, 0xb2, 0x7a, 0xb3,
0xd2, 0x2f, 0xca, 0xc6, 0x29, 0x9a, 0x39, 0x9a, 0xdd, 0xd3, 0x95, 0xf8, 0x6d, 0x00, 0x79, 0xa4,
0xcc, 0xa4, 0x36, 0x30, 0xe2, 0x9a, 0x39, 0x9a, 0xfc, 0x1d, 0x81, 0xbb, 0x89, 0x87, 0xdd, 0xf2,
0x1c, 0x39, 0x9b, 0x62, 0xe5, 0xa8, 0x66, 0x8e, 0xa6, 0x9e, 0x80, 0x27, 0x5e, 0x19, 0xcb, 0x93,
0x24, 0xfd, 0x51, 0xc4, 0x29, 0x1f, 0x15, 0x2f, 0x92, 0x7f, 0x35, 0xf5, 0x24, 0x57, 0x96, 0xd4,
0xcf, 0xa5, 0x98, 0x05, 0xb2, 0x99, 0xa3, 0xa9, 0x07, 0xbc, 0xb3, 0xe1, 0xe3, 0x53, 0xd9, 0x95,
0x74, 0x5a, 0xc9, 0x74, 0xd8, 0x9f, 0x71, 0x2d, 0x85, 0x8f, 0x53, 0x17, 0xd4, 0x47, 0x87, 0xf2,
0x79, 0x18, 0x49, 0x7d, 0x65, 0xc5, 0xb1, 0xb8, 0x75, 0x14, 0xff, 0xfb, 0x51, 0xfa, 0xad, 0x8f,
0x7c, 0xf4, 0x75, 0x3e, 0xc5, 0x29, 0xb1, 0xcd, 0x1c, 0x4d, 0xbf, 0x0d, 0xba, 0x9b, 0x78, 0x67,
0x22, 0xa3, 0xcb, 0xb4, 0x56, 0x39, 0x4a, 0xd1, 0x2a, 0xbe, 0x48, 0xf9, 0x28, 0xfd, 0xf0, 0x41,
0x3b, 0x95, 0xf9, 0x69, 0x89, 0x55, 0x3e, 0x1d, 0x3e, 0x94, 0xb8, 0x9b, 0x68, 0xad, 0xd7, 0x4e,
0x67, 0x7f, 0xda, 0x0c, 0x4c, 0xf5, 0xd3, 0xa2, 0x09, 0x3f, 0xd1, 0xe4, 0xad, 0x9d, 0xc9, 0x34,
0x28, 0xe2, 0x14, 0x83, 0x8a, 0x86, 0xf0, 0xbb, 0x89, 0x5e, 0x2e, 0x6d, 0x2a, 0xf9, 0x51, 0x05,
0xc5, 0x3f, 0xaa, 0x76, 0x7d, 0x2d, 0xa8, 0x2d, 0x4e, 0xda, 0x74, 0xd2, 0x40, 0x31, 0x86, 0x1b,
0x48, 0x69, 0x85, 0xd2, 0xb1, 0x7d, 0x42, 0x23, 0x48, 0x5e, 0x8f, 0x46, 0xb8, 0xb4, 0xd1, 0xcc,
0x51, 0x6c, 0xac, 0x30, 0x44, 0x63, 0x8e, 0x76, 0x16, 0x29, 0x26, 0x43, 0x0a, 0x0e, 0x6b, 0xe6,
0xa8, 0x68, 0xda, 0xb9, 0xa3, 0xd4, 0xee, 0xb5, 0x99, 0xa4, 0x8b, 0x88, 0x10, 0xdc, 0x45, 0xc4,
0x15, 0xfe, 0x7b, 0xe3, 0xf5, 0x6d, 0xed, 0x5c, 0xf2, 0xac, 0x4b, 0xe3, 0x9b, 0x39, 0x3a, 0x5e,
0x13, 0xbf, 0x9b, 0x28, 0xf9, 0x6a, 0xe7, 0x93, 0xea, 0x52, 0x50, 0x5c, 0x5d, 0x6a, 0x71, 0x78,
0x3d, 0xb3, 0x91, 0x52, 0xbb, 0x80, 0x02, 0xde, 0x88, 0x04, 0x8c, 0x93, 0x34, 0x73, 0x34, 0xb3,
0x05, 0xf3, 0xd3, 0xfd, 0x0b, 0xaf, 0x9a, 0x86, 0x52, 0xaf, 0x2a, 0x9b, 0x2b, 0x93, 0xae, 0x99,
0xa3, 0xfb, 0x17, 0x6f, 0x17, 0xd4, 0x1a, 0xa8, 0x76, 0x31, 0x69, 0xdf, 0x18, 0xc3, 0xed, 0xab,
0xd4, 0x4a, 0x17, 0xd4, 0x92, 0xa3, 0x76, 0x69, 0x9c, 0x2b, 0x76, 0xaa, 0x4a, 0x69, 0x92, 0x66,
0x57, 0xf8, 0xb4, 0x37, 0x90, 0xff, 0x72, 0xc8, 0x9f, 0x45, 0xd3, 0xcc, 0xd1, 0xec, 0xea, 0x20,
0xcd, 0x2e, 0xb2, 0x69, 0x97, 0x0f, 0x92, 0x19, 0x8d, 0x2e, 0xbb, 0x40, 0x67, 0x1e, 0x50, 0xe7,
0xd2, 0xde, 0x4c, 0x26, 0xa2, 0xf6, 0x25, 0x6c, 0xe6, 0xe8, 0x01, 0xd5, 0xb2, 0x87, 0xfb, 0x14,
0x9d, 0xb4, 0x2b, 0xc9, 0xde, 0xa5, 0x4c, 0xa2, 0x66, 0x8e, 0xee, 0x53, 0xb2, 0x7a, 0xb8, 0x4f,
0xe5, 0x47, 0xd3, 0x0f, 0x14, 0x1b, 0xe9, 0x63, 0x9f, 0xba, 0xd1, 0x7a, 0x66, 0xf9, 0x45, 0xbb,
0x9a, 0x5c, 0xd5, 0x19, 0x24, 0x7c, 0x55, 0x67, 0x15, 0x6e, 0xd6, 0x33, 0xeb, 0x1f, 0xda, 0xb5,
0x03, 0x04, 0x46, 0x63, 0xcc, 0xac, 0x9c, 0xac, 0x67, 0x16, 0x20, 0x34, 0x23, 0x29, 0x30, 0x83,
0x84, 0x0b, 0xcc, 0x2a, 0x5d, 0xac, 0x67, 0x56, 0x00, 0xb4, 0xeb, 0x07, 0x08, 0x8c, 0x47, 0x98,
0x55, 0x3b, 0xb8, 0x9b, 0x48, 0xc1, 0x6b, 0x6f, 0x25, 0x5d, 0x8a, 0x82, 0xe2, 0x2e, 0x45, 0x4d,
0xd6, 0x2f, 0x8d, 0x65, 0x2d, 0xb5, 0xb7, 0x93, 0x01, 0x40, 0x0a, 0xdd, 0xcc, 0xd1, 0xb1, 0x3c,
0xe7, 0xd2, 0x58, 0xe2, 0x4d, 0xbb, 0xb1, 0x9f, 0x10, 0x44, 0x27, 0x85, 0x88, 0x54, 0xdd, 0x6a,
0x46, 0xba, 0x48, 0x7b, 0x27, 0x79, 0x13, 0x1c, 0x23, 0x68, 0xe6, 0x68, 0x46, 0x92, 0x89, 0x66,
0x67, 0x1b, 0xb4, 0xd9, 0xe4, 0xb6, 0xcd, 0xa2, 0xe1, 0xdb, 0x36, 0x33, 0x53, 0xb1, 0x96, 0x15,
0xab, 0x69, 0x37, 0x93, 0x77, 0xb6, 0x71, 0x0a, 0x7e, 0x67, 0xcb, 0x88, 0xf1, 0x68, 0x76, 0xfc,
0xac, 0xfd, 0xc2, 0x81, 0x23, 0x44, 0x9a, 0x8c, 0x11, 0x8a, 0xd8, 0x3b, 0xbe, 0x56, 0x3d, 0xec,
0x77, 0x5d, 0xd3, 0xd2, 0xde, 0xcd, 0xbc, 0x56, 0x09, 0xa4, 0x72, 0xad, 0x12, 0x00, 0x7e, 0x01,
0x50, 0x63, 0x19, 0xed, 0x56, 0xf2, 0x02, 0xa0, 0xe2, 0xf8, 0x05, 0x20, 0x11, 0xf7, 0x2c, 0x8d,
0x45, 0x10, 0xda, 0x7b, 0xc9, 0x05, 0x90, 0x42, 0xf3, 0x05, 0x90, 0x02, 0x35, 0x26, 0xa0, 0xbc,
0xc2, 0x89, 0x8d, 0x3f, 0xc9, 0xc3, 0xe4, 0x66, 0xe0, 0x31, 0xb3, 0x27, 0x53, 0x16, 0x97, 0xa0,
0x2a, 0x06, 0x19, 0xfe, 0x96, 0x22, 0x8d, 0xfe, 0x27, 0x37, 0xe0, 0xf4, 0x9a, 0xe9, 0x07, 0xc8,
0xa9, 0xfc, 0x4c, 0x0d, 0x4d, 0x41, 0xc9, 0x9a, 0xa0, 0x13, 0x7c, 0xf8, 0x73, 0x01, 0xc5, 0x67,
0xbe, 0xcd, 0xa9, 0xf2, 0x30, 0x03, 0x1f, 0xe4, 0xa4, 0x78, 0x8d, 0x51, 0x1e, 0xc6, 0xd4, 0xf7,
0xe2, 0x11, 0xd3, 0xfa, 0xd8, 0xef, 0x47, 0xca, 0x00, 0xe4, 0x6b, 0x46, 0xe1, 0x63, 0xbf, 0x3e,
0x79, 0x1d, 0x8a, 0x0f, 0x57, 0x97, 0xd5, 0xe7, 0x3f, 0xc9, 0x9f, 0xd4, 0xe4, 0x58, 0xf2, 0x4e,
0x74, 0x3b, 0x7e, 0x48, 0xd7, 0x64, 0xbe, 0x02, 0x9f, 0xf7, 0x0f, 0xbc, 0x2e, 0x55, 0x50, 0x8d,
0xa9, 0x2f, 0xff, 0xf3, 0x4a, 0xee, 0xcb, 0xaf, 0xae, 0xe4, 0x7f, 0xfa, 0xd5, 0x95, 0xfc, 0x7f,
0x7c, 0x75, 0x25, 0xdf, 0xae, 0xa0, 0xb2, 0xde, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb1,
0x12, 0x46, 0xe4, 0x6f, 0x54, 0x00, 0x00,
}
func (m *Metadata) Marshal() (dAtA []byte, err error) {
@ -4107,6 +4111,13 @@ func (m *UserMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.Impersonator) > 0 {
i -= len(m.Impersonator)
copy(dAtA[i:], m.Impersonator)
i = encodeVarintEvents(dAtA, i, uint64(len(m.Impersonator)))
i--
dAtA[i] = 0x1a
}
if len(m.Login) > 0 {
i -= len(m.Login)
copy(dAtA[i:], m.Login)
@ -9279,6 +9290,10 @@ func (m *UserMetadata) Size() (n int) {
if l > 0 {
n += 1 + l + sovEvents(uint64(l))
}
l = len(m.Impersonator)
if l > 0 {
n += 1 + l + sovEvents(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@ -11714,6 +11729,38 @@ func (m *UserMetadata) Unmarshal(dAtA []byte) error {
}
m.Login = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Impersonator", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowEvents
}
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 ErrInvalidLengthEvents
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthEvents
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Impersonator = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipEvents(dAtA[iNdEx:])

View file

@ -46,6 +46,9 @@ message UserMetadata {
// Login is OS login
string Login = 2 [ (gogoproto.jsontag) = "login,omitempty" ];
// Impersonator is a user acting on behalf of another user
string Impersonator = 3 [ (gogoproto.jsontag) = "impersonator,omitempty" ];
}
// Server is a server metadata

View file

@ -111,6 +111,11 @@ type Role interface {
GetDatabaseUsers(RoleConditionType) []string
// SetDatabaseUsers sets a list of database users this role is allowed or denied access to.
SetDatabaseUsers(RoleConditionType, []string)
// GetImpersonateConditions returns conditions this role is allowed or denied to impersonate.
GetImpersonateConditions(rct RoleConditionType) ImpersonateConditions
// SetImpersonateConditions returns conditions this role is allowed or denied to impersonate.
SetImpersonateConditions(rct RoleConditionType, cond ImpersonateConditions)
}
// NewRole constructs new standard role
@ -179,6 +184,9 @@ func (r *RoleV3) Equals(other Role) bool {
if !r.GetKubernetesLabels(condition).Equals(other.GetKubernetesLabels(condition)) {
return false
}
if !r.GetImpersonateConditions(condition).Equals(other.GetImpersonateConditions(condition)) {
return false
}
}
return true
@ -472,6 +480,27 @@ func (r *RoleV3) SetDatabaseUsers(rct RoleConditionType, values []string) {
}
}
// GetImpersonateConditions returns conditions this role is allowed or denied to impersonate.
func (r *RoleV3) GetImpersonateConditions(rct RoleConditionType) ImpersonateConditions {
cond := r.Spec.Deny.Impersonate
if rct == Allow {
cond = r.Spec.Allow.Impersonate
}
if cond == nil {
return ImpersonateConditions{}
}
return *cond
}
// SetImpersonateConditions returns conditions this role is allowed or denied to impersonate.
func (r *RoleV3) SetImpersonateConditions(rct RoleConditionType, cond ImpersonateConditions) {
if rct == Allow {
r.Spec.Allow.Impersonate = &cond
} else {
r.Spec.Deny.Impersonate = &cond
}
}
// GetRules gets all allow or deny rules.
func (r *RoleV3) GetRules(rct RoleConditionType) []Rule {
if rct == Allow {
@ -592,7 +621,19 @@ func (r *RoleV3) CheckAndSetDefaults() error {
return trace.BadParameter("failed to process 'deny' rule %v: %v", i, err)
}
}
if r.Spec.Allow.Impersonate != nil {
if err := r.Spec.Allow.Impersonate.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
}
if r.Spec.Deny.Impersonate != nil {
if r.Spec.Deny.Impersonate.Where != "" {
return trace.BadParameter("'where' is not supported in deny.impersonate conditions")
}
if err := r.Spec.Deny.Impersonate.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
}
return nil
}
@ -651,6 +692,37 @@ func (r *RoleConditions) Equals(o RoleConditions) bool {
return true
}
// IsEmpty returns true if conditions are unspecified
func (i ImpersonateConditions) IsEmpty() bool {
return len(i.Users) == 0 || len(i.Roles) == 0
}
// Equals returns true if the impersonate conditions (logins, roles
// and rules) are equal and false if they are not.
func (i ImpersonateConditions) Equals(o ImpersonateConditions) bool {
if !utils.StringSlicesEqual(i.Users, o.Users) {
return false
}
if !utils.StringSlicesEqual(i.Roles, o.Roles) {
return false
}
if i.Where != o.Where {
return false
}
return true
}
// CheckAndSetDefaults checks and sets default values
func (i ImpersonateConditions) CheckAndSetDefaults() error {
if len(i.Users) != 0 && len(i.Roles) == 0 {
return trace.BadParameter("please set both impersonate.users and impersonate.roles")
}
if len(i.Users) == 0 && len(i.Roles) != 0 {
return trace.BadParameter("please set both impersonate.users and impersonate.roles")
}
return nil
}
// NewRule creates a rule based on a resource name and a list of verbs
func NewRule(resource string, verbs []string) Rule {
return Rule{

File diff suppressed because it is too large Load diff

View file

@ -965,6 +965,10 @@ message RoleConditions {
repeated string DatabaseNames = 12 [ (gogoproto.jsontag) = "db_names,omitempty" ];
// DatabaseUsers is a list of databaes users this role is allowed to connect as.
repeated string DatabaseUsers = 13 [ (gogoproto.jsontag) = "db_users,omitempty" ];
// Impersonate specifies what users and roles this role is allowed to impersonate
// by issuing certificates or other possible means.
ImpersonateConditions Impersonate = 14 [ (gogoproto.jsontag) = "impersonate,omitempty" ];
}
// AccessRequestConditions is a matcher for allow/deny restrictions on
@ -1018,6 +1022,18 @@ message Rule {
repeated string Actions = 4 [ (gogoproto.jsontag) = "actions,omitempty" ];
}
// ImpersonateConditions specifies whether users are allowed
// to issue certificates for other users or groups.
message ImpersonateConditions {
// Users is a list of resources this role is allowed to impersonate,
// could be an empty list or a Wildcard pattern
repeated string Users = 1 [ (gogoproto.jsontag) = "users,omitempty" ];
// Roles is a list of resources this role is allowed to impersonate
repeated string Roles = 2 [ (gogoproto.jsontag) = "roles,omitempty" ];
// Where specifies optional advanced matcher
string Where = 3 [ (gogoproto.jsontag) = "where,omitempty" ];
}
// BoolValue is a wrapper around bool, used in cases
// whenever bool value can have different default value when missing
message BoolValue { bool Value = 1; }

View file

@ -448,6 +448,9 @@ const (
// CertExtensionClientIP is used to embed the IP of the client that created
// the certificate.
CertExtensionClientIP = "client-ip"
// CertExtensionImpersonator is set when one user has requested certificates
// for another user
CertExtensionImpersonator = "impersonator"
)
const (

View file

@ -433,6 +433,9 @@ type certs struct {
type certRequest struct {
// user is a user to generate certificate for
user services.User
// impersonator is a user who generates the certificate,
// is set when different from the user in the certificate
impersonator string
// checker is used to perform RBAC checks.
checker services.AccessChecker
// ttl is Duration of the certificate
@ -677,11 +680,12 @@ func (a *Server) generateUserCert(req certRequest) (*certs, error) {
if err != nil {
return nil, trace.Wrap(err)
}
sshCert, err := a.Authority.GenerateUserCert(services.UserCertParams{
params := services.UserCertParams{
PrivateCASigningKey: privateKey,
CASigningAlg: sshutils.GetSigningAlgName(ca),
PublicUserKey: req.publicKey,
Username: req.user.GetName(),
Impersonator: req.impersonator,
AllowedLogins: allowedLogins,
TTL: sessionTTL,
Roles: req.checker.RoleNames(),
@ -694,7 +698,8 @@ func (a *Server) generateUserCert(req certRequest) (*certs, error) {
ActiveRequests: req.activeRequests,
MFAVerified: req.mfaVerified,
ClientIP: req.clientIP,
})
}
sshCert, err := a.Authority.GenerateUserCert(params)
if err != nil {
return nil, trace.Wrap(err)
}
@ -731,6 +736,7 @@ func (a *Server) generateUserCert(req certRequest) (*certs, error) {
}
identity := tlsca.Identity{
Username: req.user.GetName(),
Impersonator: req.impersonator,
Groups: req.checker.RoleNames(),
Principals: allowedLogins,
Usage: req.usage,
@ -1105,7 +1111,8 @@ func (a *Server) GenerateToken(ctx context.Context, req GenerateTokenRequest) (s
Code: events.TrustedClusterTokenCreateCode,
},
UserMetadata: events.UserMetadata{
User: user,
User: user,
Impersonator: clientImpersonator(ctx),
},
}); err != nil {
log.WithError(err).Warn("Failed to emit trusted cluster token create event.")
@ -1666,7 +1673,8 @@ func (a *Server) DeleteRole(ctx context.Context, name string) error {
Code: events.RoleDeletedCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: name,
@ -1743,7 +1751,8 @@ func (a *Server) CreateAccessRequest(ctx context.Context, req services.AccessReq
Code: events.AccessRequestCreateCode,
},
UserMetadata: events.UserMetadata{
User: req.GetUser(),
User: req.GetUser(),
Impersonator: clientImpersonator(ctx),
},
Roles: req.GetRoles(),
RequestID: req.GetName(),

View file

@ -1194,21 +1194,51 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC
var roles []string
var traits wrappers.Traits
switch {
case a.hasBuiltinRole(string(teleport.RoleAdmin)):
// If it's an admin generating the certificate, the roles and traits for
// the user have to be fetched from the backend. This should be safe since
// this is typically done against a local user.
user, err := a.GetUser(req.Username, false)
if err != nil {
return nil, trace.Wrap(err)
// this prevents clients who have no chance at getting a cert and impersonating anyone
// from enumerating local users and hitting database
if !a.hasBuiltinRole(string(teleport.RoleAdmin)) && !a.context.Checker.CanImpersonateSomeone() && req.Username != a.context.User.GetName() {
return nil, trace.AccessDenied("access denied: impersonation is not allowed")
}
// Prohibit recursive impersonation behavior:
//
// Alice can impersonate Bob
// Bob can impersonate Grace <- this code block prohibits the escape
//
// Allow cases:
//
// Alice can impersonate Bob
//
// Bob (impersonated by Alice) can renew the cert with route to cluster
//
if a.context.Identity != nil && a.context.Identity.GetIdentity().Impersonator != "" {
if len(req.AccessRequests) > 0 {
return nil, trace.AccessDenied("access denied: impersonated user can not request new roles")
}
roles = user.GetRoles()
traits = user.GetTraits()
case req.Username == a.context.User.GetName():
// user is requesting TTL for themselves,
// limit the TTL to the duration of the session, to prevent
// users renewing their certificates forever
if req.Username != a.context.User.GetName() {
return nil, trace.AccessDenied("access denied: impersonated user can not impersonate anyone else")
}
}
// Extract the user and role set for whom the certificate will be generated.
// This should be safe since this is typically done against a local user.
//
// This call bypasses RBAC check for users read on purpose.
// Users who are allowed to impersonate other users might not have
// permissions to read user data.
user, err := a.authServer.GetUser(req.Username, false)
if err != nil {
log.WithError(err).Debugf("Could not impersonate user %v. The user could not be fetched from local store.", req.Username)
return nil, trace.AccessDenied("access denied")
}
if user.GetCreatedBy().Connector != nil {
log.Warningf("User %v tried to issue a cert for externally managed user %v, this is not supported.", a.context.User.GetName(), req.Username)
return nil, trace.AccessDenied("access denied")
}
// For users renewing certificates limit the TTL to the duration of the session, to prevent
// users renewing certificates forever.
if req.Username == a.context.User.GetName() {
expires := a.context.Identity.GetIdentity().Expires
if expires.IsZero() {
log.Warningf("Encountered identity with no expiry: %v and denied request. Must be internal logic error.", a.context.Identity)
@ -1220,31 +1250,22 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC
if req.Expires.Before(a.authServer.GetClock().Now()) {
return nil, trace.AccessDenied("access denied: client credentials have expired, please relogin.")
}
// If the user is generating a certificate, the roles and traits come from
// the logged in identity.
}
// If the user is generating a certificate, the roles and traits come from the logged in identity.
if req.Username == a.context.User.GetName() {
roles, traits, err = services.ExtractFromIdentity(a.authServer, a.context.Identity.GetIdentity())
if err != nil {
return nil, trace.Wrap(err)
}
default:
err := trace.AccessDenied("user %q has requested to generate certs for %q.", a.context.User.GetName(), req.Username)
log.Warning(err)
if err := a.authServer.emitter.EmitAuditEvent(a.CloseContext(), &events.UserLogin{
Metadata: events.Metadata{
Type: events.UserLoginEvent,
Code: events.UserLocalLoginFailureCode,
},
Method: events.LoginMethodClientCert,
Status: events.Status{
Success: false,
Error: trace.Unwrap(err).Error(),
UserMessage: err.Error(),
},
}); err != nil {
log.WithError(err).Warn("Failed to emit local login failure event.")
} else {
// Do not allow combining impersonation and access requests
if len(req.AccessRequests) > 0 {
log.WithError(err).Warningf("User %v tried to issue a cert for %v and added access requests. This is not supported.", a.context.User.GetName(), req.Username)
return nil, trace.AccessDenied("access denied")
}
// this error is vague on purpose, it should not happen unless someone is trying something out of loop
return nil, trace.AccessDenied("this request can be only executed by an admin")
roles = user.GetRoles()
traits = user.GetTraits()
}
if len(req.AccessRequests) > 0 {
@ -1284,14 +1305,45 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC
roles = utils.Deduplicate(roles)
}
// Extract the user and role set for whom the certificate will be generated.
user, err := a.GetUser(req.Username, false)
parsedRoles, err := services.FetchRoleList(roles, a.authServer, traits)
if err != nil {
return nil, trace.Wrap(err)
}
checker, err := services.FetchRoles(roles, a.authServer, traits)
if err != nil {
return nil, trace.Wrap(err)
// add implicit roles to the set and build a checker
checker := services.NewRoleSet(parsedRoles...)
switch {
case a.hasBuiltinRole(string(teleport.RoleAdmin)):
// builtin admins can impersonate anyone
// this is required for local tctl commands to work
case req.Username == a.context.User.GetName():
// users can impersonate themselves
default:
// check if this user is allowed to impersonate other users
err = a.context.Checker.CheckImpersonate(a.context.User, user, parsedRoles)
// adjust session TTL based on the impersonated role set limit
ttl := req.Expires.Sub(a.authServer.GetClock().Now())
ttl = checker.AdjustSessionTTL(ttl)
req.Expires = a.authServer.GetClock().Now().Add(ttl)
if err != nil {
log.Warning(err)
err := trace.AccessDenied("user %q has requested to generate certs for %q.", a.context.User.GetName(), roles)
if err := a.authServer.emitter.EmitAuditEvent(a.CloseContext(), &events.UserLogin{
Metadata: events.Metadata{
Type: events.UserLoginEvent,
Code: events.UserLocalLoginFailureCode,
},
Method: events.LoginMethodClientCert,
Status: events.Status{
Success: false,
Error: trace.Unwrap(err).Error(),
UserMessage: err.Error(),
},
}); err != nil {
log.WithError(err).Warn("Failed to emit local login failure event.")
}
return nil, trace.Wrap(err)
}
}
// Generate certificate, note that the roles TTL will be ignored because
@ -1314,6 +1366,12 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC
AccessRequests: req.AccessRequests,
},
}
if user.GetName() != a.context.User.GetName() {
certReq.impersonator = a.context.User.GetName()
} else if a.context.Identity != nil && a.context.Identity.GetIdentity().Impersonator != "" {
// impersonating users can receive new certs
certReq.impersonator = a.context.Identity.GetIdentity().Impersonator
}
switch req.Usage {
case proto.UserCertsRequest_Database:
certReq.usage = []string{teleport.UsageDatabaseOnly}

View file

@ -72,7 +72,8 @@ func (a *Server) upsertGithubConnector(ctx context.Context, connector services.G
Code: events.GithubConnectorCreatedCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: connector.GetName(),
@ -96,7 +97,8 @@ func (a *Server) deleteGithubConnector(ctx context.Context, connectorName string
Code: events.GithubConnectorDeletedCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: connectorName,

View file

@ -28,6 +28,7 @@ import (
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
authority "github.com/gravitational/teleport/lib/auth/testauthority"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/backend/memory"
@ -633,6 +634,26 @@ func (t *TestTLSServer) CloneClient(clt *Client) *Client {
return newClient
}
// NewClientWithCert creates a new client using given cert and private key
func (t *TestTLSServer) NewClientWithCert(clientCert tls.Certificate) *Client {
tlsConfig, err := t.Identity.TLSConfig(t.AuthServer.CipherSuites)
if err != nil {
panic(err)
}
tlsConfig.Time = t.AuthServer.AuthServer.clock.Now
tlsConfig.Certificates = []tls.Certificate{clientCert}
newClient, err := NewClient(client.Config{
Addrs: []string{t.Addr().String()},
Credentials: []client.Credentials{
client.LoadTLS(tlsConfig),
},
})
if err != nil {
panic(err)
}
return newClient
}
// NewClient returns new client to test server authenticated with identity
func (t *TestTLSServer) NewClient(identity TestIdentity) (*Client, error) {
tlsConfig, err := t.ClientTLSConfig(identity)
@ -736,6 +757,29 @@ func CreateUserRoleAndRequestable(clt clt, username string, rolename string) (se
return user, nil
}
// CreateUser creates user and role and assignes role to a user, used in tests
func CreateUser(clt clt, username string, roles ...types.Role) (types.User, error) {
ctx := context.TODO()
user, err := services.NewUser(username)
if err != nil {
return nil, trace.Wrap(err)
}
for _, role := range roles {
err = clt.UpsertRole(ctx, role)
if err != nil {
return nil, trace.Wrap(err)
}
user.AddRole(role.GetName())
}
err = clt.UpsertUser(user)
if err != nil {
return nil, trace.Wrap(err)
}
return user, nil
}
// CreateUserAndRole creates user and role and assignes role to a user, used in tests
func CreateUserAndRole(clt clt, username string, allowedLogins []string) (services.User, services.Role, error) {
ctx := context.TODO()

View file

@ -283,6 +283,9 @@ func (k *Keygen) GenerateUserCertWithoutValidation(c services.UserCertParams) ([
if c.ClientIP != "" {
cert.Permissions.Extensions[teleport.CertExtensionClientIP] = c.ClientIP
}
if c.Impersonator != "" {
cert.Permissions.Extensions[teleport.CertExtensionImpersonator] = c.Impersonator
}
// Add roles, traits, and route to cluster in the certificate extensions if
// the standard format was requested. Certificate extensions are not included

View file

@ -155,7 +155,8 @@ func (a *Server) UpsertOIDCConnector(ctx context.Context, connector services.OID
Code: events.OIDCConnectorCreatedCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: connector.GetName(),
@ -178,7 +179,8 @@ func (a *Server) DeleteOIDCConnector(ctx context.Context, connectorName string)
Code: events.OIDCConnectorDeletedCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: connectorName,

View file

@ -638,6 +638,18 @@ func clientUsername(ctx context.Context) string {
return identity.Username
}
// clientImpersonator returns the impersonator username of a remote client
// making the call. If not present, returns an empty string
func clientImpersonator(ctx context.Context) string {
userI := ctx.Value(ContextUser)
userWithIdentity, ok := userI.(IdentityGetter)
if !ok {
return ""
}
identity := userWithIdentity.GetIdentity()
return identity.Impersonator
}
// LocalUser is a local user
type LocalUser struct {
// Username is local username

View file

@ -138,7 +138,8 @@ func (s *Server) CreateResetPasswordToken(ctx context.Context, req CreateResetPa
Code: events.ResetPasswordTokenCreateCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: req.Name,

View file

@ -46,7 +46,8 @@ func (a *Server) UpsertSAMLConnector(ctx context.Context, connector services.SAM
Code: events.SAMLConnectorCreatedCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: connector.GetName(),
@ -69,7 +70,8 @@ func (a *Server) DeleteSAMLConnector(ctx context.Context, connectorName string)
Code: events.SAMLConnectorDeletedCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: connectorName,

View file

@ -156,11 +156,13 @@ func (s *AuthSuite) GenerateUserCert(c *check.C) {
c.Assert(err, check.IsNil)
inRoles := []string{"role-1", "role-2"}
impersonator := "alice"
cert, err = s.A.GenerateUserCert(services.UserCertParams{
PrivateCASigningKey: priv,
CASigningAlg: defaults.CASignatureAlgorithm,
PublicUserKey: pub,
Username: "user",
Impersonator: impersonator,
AllowedLogins: []string{"root"},
TTL: time.Hour,
PermitAgentForwarding: true,
@ -174,6 +176,9 @@ func (s *AuthSuite) GenerateUserCert(c *check.C) {
outRoles, err := services.UnmarshalCertRoles(parsedCert.Extensions[teleport.CertExtensionTeleportRoles])
c.Assert(err, check.IsNil)
c.Assert(outRoles, check.DeepEquals, inRoles)
outImpersonator := parsedCert.Extensions[teleport.CertExtensionImpersonator]
c.Assert(outImpersonator, check.DeepEquals, impersonator)
}
func checkCertExpiry(cert []byte, after, before time.Time) error {

View file

@ -21,6 +21,7 @@ import (
"bytes"
"compress/gzip"
"context"
"crypto"
"crypto/tls"
"encoding/base32"
"encoding/json"
@ -1860,22 +1861,24 @@ func TestGenerateCerts(t *testing.T) {
require.NoError(t, err)
require.Contains(t, hostCert.ValidPrincipals, "example.com")
// attempt to elevate privileges by getting admin role in the certificate
_, err = hostClient.GenerateServerKeys(
GenerateServerKeysRequest{
HostID: hostID,
NodeName: srv.AuthServer.ClusterName,
Roles: teleport.Roles{teleport.RoleAdmin},
})
require.True(t, trace.IsAccessDenied(err))
t.Run("HostClients", func(t *testing.T) {
// attempt to elevate privileges by getting admin role in the certificate
_, err = hostClient.GenerateServerKeys(
GenerateServerKeysRequest{
HostID: hostID,
NodeName: srv.AuthServer.ClusterName,
Roles: teleport.Roles{teleport.RoleAdmin},
})
require.True(t, trace.IsAccessDenied(err))
// attempt to get certificate for different host id
_, err = hostClient.GenerateServerKeys(GenerateServerKeysRequest{
HostID: "some-other-host-id",
NodeName: srv.AuthServer.ClusterName,
Roles: teleport.Roles{teleport.RoleNode},
// attempt to get certificate for different host id
_, err = hostClient.GenerateServerKeys(GenerateServerKeysRequest{
HostID: "some-other-host-id",
NodeName: srv.AuthServer.ClusterName,
Roles: teleport.Roles{teleport.RoleNode},
})
require.True(t, trace.IsAccessDenied(err))
})
require.True(t, trace.IsAccessDenied(err))
user1, userRole, err := CreateUserAndRole(srv.Auth(), "user1", []string{"user1"})
require.NoError(t, err)
@ -1883,52 +1886,37 @@ func TestGenerateCerts(t *testing.T) {
user2, userRole2, err := CreateUserAndRole(srv.Auth(), "user2", []string{"user2"})
require.NoError(t, err)
// unauthenticated client should NOT be able to generate a user cert without auth
nopClient, err := srv.NewClient(TestNop())
require.NoError(t, err)
t.Run("Nop", func(t *testing.T) {
// unauthenticated client should NOT be able to generate a user cert without auth
nopClient, err := srv.NewClient(TestNop())
require.NoError(t, err)
_, err = nopClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
_, err = nopClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
})
require.Error(t, err)
require.True(t, trace.IsAccessDenied(err), err.Error())
})
require.Error(t, err)
require.True(t, trace.IsAccessDenied(err))
require.Contains(t, err.Error(), "this request can be only executed by an admin")
// User can't generate certificates for another user
testUser2 := TestUser(user2.GetName())
testUser2.TTL = time.Hour
userClient2, err := srv.NewClient(testUser2)
require.NoError(t, err)
_, err = userClient2.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
t.Run("ImpersonateDeny", func(t *testing.T) {
// User can't generate certificates for another user by default
_, err = userClient2.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
})
require.Error(t, err)
require.True(t, trace.IsAccessDenied(err))
})
require.Error(t, err)
require.True(t, trace.IsAccessDenied(err))
require.Contains(t, err.Error(), "this request can be only executed by an admin")
rc1, err := types.NewRemoteCluster("cluster1")
require.NoError(t, err)
err = srv.Auth().CreateRemoteCluster(rc1)
require.NoError(t, err)
// User can renew their certificates, however the TTL will be limited
// to the TTL of their session for both SSH and x509 certs and
// that route to cluster will be encoded in the cert metadata
userCerts, err := userClient2.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user2.GetName(),
Expires: time.Now().Add(100 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
RouteToCluster: rc1.GetName(),
})
require.NoError(t, err)
parseCert := func(sshCert []byte) (*ssh.Certificate, time.Duration) {
parsedCert, err := sshutils.ParseCertificate(sshCert)
@ -1936,120 +1924,266 @@ func TestGenerateCerts(t *testing.T) {
validBefore := time.Unix(int64(parsedCert.ValidBefore), 0)
return parsedCert, time.Until(validBefore)
}
_, diff := parseCert(userCerts.SSH)
require.Less(t, int64(diff), int64(testUser2.TTL))
tlsCert, err := tlsca.ParseCertificatePEM(userCerts.TLS)
require.NoError(t, err)
identity, err := tlsca.FromSubject(tlsCert.Subject, tlsCert.NotAfter)
require.NoError(t, err)
require.True(t, identity.Expires.Before(time.Now().Add(testUser2.TTL)))
require.Equal(t, identity.RouteToCluster, rc1.GetName())
clock := srv.Auth().GetClock()
t.Run("ImpersonateAllow", func(t *testing.T) {
// Super impersonator impersonate anyone and login as root
maxSessionTTL := 300 * time.Hour
superImpersonatorRole, err := services.NewRole("superimpersonator", types.RoleSpecV3{
Options: types.RoleOptions{
MaxSessionTTL: types.Duration(maxSessionTTL),
},
Allow: types.RoleConditions{
Logins: []string{"root"},
Impersonate: &types.ImpersonateConditions{
Users: []string{types.Wildcard},
Roles: []string{types.Wildcard},
},
Rules: []types.Rule{},
},
})
require.NoError(t, err)
superImpersonator, err := CreateUser(srv.Auth(), "superimpersonator", superImpersonatorRole)
require.NoError(t, err)
// Admin should be allowed to generate certs with TTL longer than max.
adminClient, err := srv.NewClient(TestAdmin())
require.NoError(t, err)
// Impersonator can generate certificates for super impersonator
role, err := services.NewRole("impersonate", types.RoleSpecV3{
Allow: types.RoleConditions{
Logins: []string{superImpersonator.GetName()},
Impersonate: &types.ImpersonateConditions{
Users: []string{superImpersonator.GetName()},
Roles: []string{superImpersonatorRole.GetName()},
},
},
})
require.NoError(t, err)
impersonator, err := CreateUser(srv.Auth(), "impersonator", role)
require.NoError(t, err)
userCerts, err = adminClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(40 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
iUser := TestUser(impersonator.GetName())
iUser.TTL = time.Hour
iClient, err := srv.NewClient(iUser)
require.NoError(t, err)
// can impersonate super impersonator and request certs
// longer than their own TTL, but not exceeding super impersonator's max session ttl
userCerts, err := iClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: superImpersonator.GetName(),
Expires: clock.Now().Add(1000 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
})
require.NoError(t, err)
_, diff := parseCert(userCerts.SSH)
require.Less(t, int64(diff), int64(iUser.TTL))
tlsCert, err := tlsca.ParseCertificatePEM(userCerts.TLS)
require.NoError(t, err)
identity, err := tlsca.FromSubject(tlsCert.Subject, tlsCert.NotAfter)
require.NoError(t, err)
// Because the original request has maxed out the possible max
// session TTL, it will be adjusted to exactly the value
require.Equal(t, identity.Expires.Sub(clock.Now()), maxSessionTTL)
require.Equal(t, impersonator.GetName(), identity.Impersonator)
require.Equal(t, superImpersonator.GetName(), identity.Username)
// impersonator can't impersonate user1
_, err = iClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: clock.Now().Add(time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
})
require.Error(t, err)
require.IsType(t, &trace.AccessDeniedError{}, err)
_, privateKeyPEM, err := utils.MarshalPrivateKey(privateKey.(crypto.Signer))
require.NoError(t, err)
clientCert, err := tls.X509KeyPair(userCerts.TLS, privateKeyPEM)
require.NoError(t, err)
// client that uses impersonated certificate can't impersonate other users
// although super impersonator's roles allow it
impersonatedClient := srv.NewClientWithCert(clientCert)
_, err = impersonatedClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
})
require.Error(t, err)
require.IsType(t, &trace.AccessDeniedError{}, err)
require.Contains(t, err.Error(), "impersonated user can not impersonate anyone else")
// but can renew their own cert, for example set route to cluster
rc, err := types.NewRemoteCluster("cluster-remote")
require.NoError(t, err)
err = srv.Auth().CreateRemoteCluster(rc)
require.NoError(t, err)
userCerts, err = impersonatedClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: superImpersonator.GetName(),
Expires: clock.Now().Add(time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
RouteToCluster: rc.GetName(),
})
require.NoError(t, err)
// Make sure impersonator was not lost in the renewed cert
tlsCert, err = tlsca.ParseCertificatePEM(userCerts.TLS)
require.NoError(t, err)
identity, err = tlsca.FromSubject(tlsCert.Subject, tlsCert.NotAfter)
require.NoError(t, err)
require.Equal(t, identity.Expires.Sub(clock.Now()), time.Hour)
require.Equal(t, impersonator.GetName(), identity.Impersonator)
require.Equal(t, superImpersonator.GetName(), identity.Username)
})
require.NoError(t, err)
parsedCert, diff := parseCert(userCerts.SSH)
require.Less(t, int64(defaults.MaxCertDuration), int64(diff))
t.Run("Renew", func(t *testing.T) {
testUser2 := TestUser(user2.GetName())
testUser2.TTL = time.Hour
userClient2, err := srv.NewClient(testUser2)
require.NoError(t, err)
// user should have agent forwarding (default setting)
require.Contains(t, parsedCert.Extensions, teleport.CertExtensionPermitAgentForwarding)
rc1, err := types.NewRemoteCluster("cluster1")
require.NoError(t, err)
err = srv.Auth().CreateRemoteCluster(rc1)
require.NoError(t, err)
// user should not have X11 forwarding (default setting)
require.NotContains(t, parsedCert.Extensions, teleport.CertExtensionPermitX11Forwarding)
// User can renew their certificates, however the TTL will be limited
// to the TTL of their session for both SSH and x509 certs and
// that route to cluster will be encoded in the cert metadata
userCerts, err := userClient2.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user2.GetName(),
Expires: time.Now().Add(100 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
RouteToCluster: rc1.GetName(),
})
require.NoError(t, err)
// now update role to permit agent and X11 forwarding
roleOptions := userRole.GetOptions()
roleOptions.ForwardAgent = services.NewBool(true)
roleOptions.PermitX11Forwarding = services.NewBool(true)
userRole.SetOptions(roleOptions)
err = srv.Auth().UpsertRole(ctx, userRole)
require.NoError(t, err)
_, diff := parseCert(userCerts.SSH)
require.Less(t, int64(diff), int64(testUser2.TTL))
userCerts, err = adminClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(1 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
tlsCert, err := tlsca.ParseCertificatePEM(userCerts.TLS)
require.NoError(t, err)
identity, err := tlsca.FromSubject(tlsCert.Subject, tlsCert.NotAfter)
require.NoError(t, err)
require.True(t, identity.Expires.Before(time.Now().Add(testUser2.TTL)))
require.Equal(t, identity.RouteToCluster, rc1.GetName())
})
require.NoError(t, err)
parsedCert, _ = parseCert(userCerts.SSH)
// user should get agent forwarding
require.Contains(t, parsedCert.Extensions, teleport.CertExtensionPermitAgentForwarding)
t.Run("Admin", func(t *testing.T) {
// Admin should be allowed to generate certs with TTL longer than max.
adminClient, err := srv.NewClient(TestAdmin())
require.NoError(t, err)
// user should get X11 forwarding
require.Contains(t, parsedCert.Extensions, teleport.CertExtensionPermitX11Forwarding)
userCerts, err := adminClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(40 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
})
require.NoError(t, err)
// apply HTTP Auth to generate user cert:
userCerts, err = adminClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
parsedCert, diff := parseCert(userCerts.SSH)
require.Less(t, int64(defaults.MaxCertDuration), int64(diff))
// user should have agent forwarding (default setting)
require.Contains(t, parsedCert.Extensions, teleport.CertExtensionPermitAgentForwarding)
// user should not have X11 forwarding (default setting)
require.NotContains(t, parsedCert.Extensions, teleport.CertExtensionPermitX11Forwarding)
// now update role to permit agent and X11 forwarding
roleOptions := userRole.GetOptions()
roleOptions.ForwardAgent = services.NewBool(true)
roleOptions.PermitX11Forwarding = services.NewBool(true)
userRole.SetOptions(roleOptions)
err = srv.Auth().UpsertRole(ctx, userRole)
require.NoError(t, err)
userCerts, err = adminClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(1 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
})
require.NoError(t, err)
parsedCert, _ = parseCert(userCerts.SSH)
// user should get agent forwarding
require.Contains(t, parsedCert.Extensions, teleport.CertExtensionPermitAgentForwarding)
// user should get X11 forwarding
require.Contains(t, parsedCert.Extensions, teleport.CertExtensionPermitX11Forwarding)
// apply HTTP Auth to generate user cert:
userCerts, err = adminClient.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user1.GetName(),
Expires: time.Now().Add(time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
})
require.NoError(t, err)
_, _, _, _, err = ssh.ParseAuthorizedKey(userCerts.SSH)
require.NoError(t, err)
})
require.NoError(t, err)
_, _, _, _, err = ssh.ParseAuthorizedKey(userCerts.SSH)
require.NoError(t, err)
t.Run("DenyLeaf", func(t *testing.T) {
// User can't generate certificates for an unknown leaf cluster.
_, err = userClient2.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user2.GetName(),
Expires: time.Now().Add(100 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
RouteToCluster: "unknown_cluster",
})
require.Error(t, err)
// User can't generate certificates for an unknown leaf cluster.
_, err = userClient2.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user2.GetName(),
Expires: time.Now().Add(100 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
RouteToCluster: "unknown_cluster",
rc2, err := types.NewRemoteCluster("cluster2")
require.NoError(t, err)
meta := rc2.GetMetadata()
meta.Labels = map[string]string{"env": "prod"}
rc2.SetMetadata(meta)
err = srv.Auth().CreateRemoteCluster(rc2)
require.NoError(t, err)
// User can't generate certificates for leaf cluster they don't have access
// to due to labels.
_, err = userClient2.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user2.GetName(),
Expires: time.Now().Add(100 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
RouteToCluster: rc2.GetName(),
})
require.Error(t, err)
userRole2.SetClusterLabels(types.Allow, types.Labels{"env": utils.Strings{"prod"}})
err = srv.Auth().UpsertRole(ctx, userRole2)
require.NoError(t, err)
// User can generate certificates for leaf cluster they do have access to.
userCerts, err := userClient2.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user2.GetName(),
Expires: time.Now().Add(100 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
RouteToCluster: rc2.GetName(),
})
require.NoError(t, err)
tlsCert, err := tlsca.ParseCertificatePEM(userCerts.TLS)
require.NoError(t, err)
identity, err := tlsca.FromSubject(tlsCert.Subject, tlsCert.NotAfter)
require.NoError(t, err)
require.Equal(t, identity.RouteToCluster, rc2.GetName())
})
require.Error(t, err)
rc2, err := types.NewRemoteCluster("cluster2")
require.NoError(t, err)
meta := rc2.GetMetadata()
meta.Labels = map[string]string{"env": "prod"}
rc2.SetMetadata(meta)
err = srv.Auth().CreateRemoteCluster(rc2)
require.NoError(t, err)
// User can't generate certificates for leaf cluster they don't have access
// to due to labels.
_, err = userClient2.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user2.GetName(),
Expires: time.Now().Add(100 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
RouteToCluster: rc2.GetName(),
})
require.Error(t, err)
userRole2.SetClusterLabels(types.Allow, types.Labels{"env": utils.Strings{"prod"}})
err = srv.Auth().UpsertRole(ctx, userRole2)
require.NoError(t, err)
// User can generate certificates for leaf cluster they do have access to.
userCerts, err = userClient2.GenerateUserCerts(ctx, proto.UserCertsRequest{
PublicKey: pub,
Username: user2.GetName(),
Expires: time.Now().Add(100 * time.Hour).UTC(),
Format: teleport.CertificateFormatStandard,
RouteToCluster: rc2.GetName(),
})
require.NoError(t, err)
tlsCert, err = tlsca.ParseCertificatePEM(userCerts.TLS)
require.NoError(t, err)
identity, err = tlsca.FromSubject(tlsCert.Subject, tlsCert.NotAfter)
require.NoError(t, err)
require.Equal(t, identity.RouteToCluster, rc2.GetName())
}
// TestGenerateAppToken checks the identity of the caller and makes sure only

View file

@ -147,7 +147,8 @@ func (a *Server) UpsertTrustedCluster(ctx context.Context, trustedCluster servic
Code: events.TrustedClusterCreateCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: trustedCluster.GetName(),
@ -220,7 +221,8 @@ func (a *Server) DeleteTrustedCluster(ctx context.Context, name string) error {
Code: events.TrustedClusterDeleteCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: name,

View file

@ -62,7 +62,8 @@ func (s *Server) CreateUser(ctx context.Context, user services.User) error {
Code: events.UserCreateCode,
},
UserMetadata: events.UserMetadata{
User: user.GetCreatedBy().User.Name,
User: user.GetCreatedBy().User.Name,
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: user.GetName(),
@ -96,7 +97,8 @@ func (s *Server) UpdateUser(ctx context.Context, user services.User) error {
Code: events.UserUpdateCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: user.GetName(),
@ -173,7 +175,8 @@ func (s *Server) DeleteUser(ctx context.Context, user string) error {
Code: events.UserDeleteCode,
},
UserMetadata: events.UserMetadata{
User: clientUsername(ctx),
User: clientUsername(ctx),
Impersonator: clientImpersonator(ctx),
},
ResourceMetadata: events.ResourceMetadata{
Name: user,

View file

@ -734,8 +734,9 @@ func (f *Forwarder) exec(ctx *authContext, w http.ResponseWriter, req *http.Requ
WithMFA: ctx.Identity.GetIdentity().MFAVerified,
},
UserMetadata: events.UserMetadata{
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
Impersonator: ctx.Identity.GetIdentity().Impersonator,
},
TerminalSize: params.Serialize(),
KubernetesClusterMetadata: ctx.eventClusterMeta(),
@ -776,8 +777,9 @@ func (f *Forwarder) exec(ctx *authContext, w http.ResponseWriter, req *http.Requ
WithMFA: ctx.Identity.GetIdentity().MFAVerified,
},
UserMetadata: events.UserMetadata{
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
Impersonator: ctx.Identity.GetIdentity().Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
RemoteAddr: req.RemoteAddr,
@ -859,8 +861,9 @@ func (f *Forwarder) exec(ctx *authContext, w http.ResponseWriter, req *http.Requ
WithMFA: ctx.Identity.GetIdentity().MFAVerified,
},
UserMetadata: events.UserMetadata{
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
Impersonator: ctx.Identity.GetIdentity().Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
RemoteAddr: req.RemoteAddr,
@ -890,8 +893,9 @@ func (f *Forwarder) exec(ctx *authContext, w http.ResponseWriter, req *http.Requ
WithMFA: ctx.Identity.GetIdentity().MFAVerified,
},
UserMetadata: events.UserMetadata{
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
Impersonator: ctx.Identity.GetIdentity().Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
RemoteAddr: req.RemoteAddr,
@ -926,8 +930,9 @@ func (f *Forwarder) exec(ctx *authContext, w http.ResponseWriter, req *http.Requ
WithMFA: ctx.Identity.GetIdentity().MFAVerified,
},
UserMetadata: events.UserMetadata{
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
Impersonator: ctx.Identity.GetIdentity().Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
RemoteAddr: req.RemoteAddr,
@ -989,8 +994,9 @@ func (f *Forwarder) portForward(ctx *authContext, w http.ResponseWriter, req *ht
Code: events.PortForwardCode,
},
UserMetadata: events.UserMetadata{
Login: ctx.User.GetName(),
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
User: ctx.User.GetName(),
Impersonator: ctx.Identity.GetIdentity().Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: sess.teleportCluster.targetAddr,
@ -1179,8 +1185,9 @@ func (f *Forwarder) catchAll(ctx *authContext, w http.ResponseWriter, req *http.
Code: events.KubeRequestCode,
},
UserMetadata: events.UserMetadata{
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
User: ctx.User.GetName(),
Login: ctx.User.GetName(),
Impersonator: ctx.Identity.GetIdentity().Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
RemoteAddr: req.RemoteAddr,

View file

@ -245,6 +245,8 @@ type UserCertParams struct {
TTL time.Duration
// Username is teleport username
Username string
// Impersonator is set when a user requests certificate for another user
Impersonator string
// AllowedLogins is a list of SSH principals
AllowedLogins []string
// PermitX11Forwarding permits X11 forwarding for this cert

View file

@ -0,0 +1,85 @@
/*
Copyright 2021 Gravitational, Inc.
Licensed 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 services
import (
"strings"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/trace"
"github.com/vulcand/predicate"
)
// impersonateContext is a default rule context used in teleport
type impersonateContext struct {
// user is currently authenticated user
user types.User
// impersonateRole is a role to impersonate
impersonateRole types.Role
// impersonateUser is a user to impersonate
impersonateUser types.User
}
// getIdentifier returns identifier defined in a context
func (ctx *impersonateContext) getIdentifier(fields []string) (interface{}, error) {
switch fields[0] {
case UserIdentifier:
return predicate.GetFieldByTag(ctx.user, teleport.JSON, fields[1:])
case ImpersonateUserIdentifier:
return predicate.GetFieldByTag(ctx.impersonateUser, teleport.JSON, fields[1:])
case ImpersonateRoleIdentifier:
return predicate.GetFieldByTag(ctx.impersonateRole, teleport.JSON, fields[1:])
default:
return nil, trace.NotFound("%v is not defined", strings.Join(fields, "."))
}
}
// matchesImpersonateWhere returns true if Where rule matches.
// Empty Where block always matches.
func matchesImpersonateWhere(cond types.ImpersonateConditions, parser predicate.Parser) (bool, error) {
if cond.Where == "" {
return true, nil
}
ifn, err := parser.Parse(cond.Where)
if err != nil {
return false, trace.Wrap(err)
}
fn, ok := ifn.(predicate.BoolPredicate)
if !ok {
return false, trace.BadParameter("invalid predicate type for where expression: %v", cond.Where)
}
return fn(), nil
}
// newImpersonateWhereParser returns standard parser for `where` section in impersonate rules
func newImpersonateWhereParser(ctx *impersonateContext) (predicate.Parser, error) {
return predicate.NewParser(predicate.Def{
Operators: predicate.Operators{
AND: predicate.And,
OR: predicate.Or,
NOT: predicate.Not,
},
Functions: map[string]interface{}{
"equals": predicate.Equals,
"contains": predicate.Contains,
},
GetIdentifier: ctx.getIdentifier,
GetProperty: GetStringMapValue,
})
}

View file

@ -0,0 +1,272 @@
/*
Copyright 2021 Gravitational, Inc.
Licensed 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 services
import (
"fmt"
"testing"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
)
func TestCheckImpersonate(t *testing.T) {
noLabelsRole := &types.RoleV3{
Metadata: types.Metadata{
Name: "no-labels",
Namespace: defaults.Namespace,
},
Spec: types.RoleSpecV3{
Allow: types.RoleConditions{
Namespaces: []string{defaults.Namespace},
},
},
}
wildcardRole := &types.RoleV3{
Metadata: types.Metadata{
Name: "wildcard",
Namespace: defaults.Namespace,
},
Spec: types.RoleSpecV3{
Allow: types.RoleConditions{
Impersonate: &types.ImpersonateConditions{
Users: []string{types.Wildcard},
Roles: []string{types.Wildcard},
},
},
},
}
wildcardDenyRole := &types.RoleV3{
Metadata: types.Metadata{
Name: "wildcard-deny-user",
Namespace: defaults.Namespace,
},
Spec: types.RoleSpecV3{
Deny: types.RoleConditions{
Impersonate: &types.ImpersonateConditions{
Users: []string{types.Wildcard},
Roles: []string{types.Wildcard},
},
},
},
}
type props struct {
traits map[string][]string
labels map[string]string
}
var empty props
newUser := func(name string, props props) types.User {
u := &UserV2{
Kind: types.KindUser,
Version: types.V2,
Metadata: types.Metadata{
Name: name,
Namespace: defaults.Namespace,
Labels: props.labels,
},
Spec: types.UserSpecV2{
Traits: props.traits,
},
}
if err := u.CheckAndSetDefaults(); err != nil {
t.Fatal(err)
}
return u
}
type impersonate struct {
name string
allowed bool
user types.User
roles []types.Role
}
testCases := []struct {
name string
user types.User
roles []types.Role
checks []impersonate
}{
{
name: "empty role set can impersonate no other user",
user: newUser("alice", empty),
checks: []impersonate{
{
allowed: false,
user: newUser("bob", empty),
roles: []types.Role{
noLabelsRole,
},
},
},
},
{
name: "wildcard role can impersonate any user or role",
user: newUser("alice", empty),
roles: []types.Role{
wildcardRole,
},
checks: []impersonate{
{
allowed: true,
user: newUser("bob", empty),
roles: []types.Role{
noLabelsRole,
},
},
},
},
{
name: "wildcard deny user overrides wildcard allow",
user: newUser("alice", empty),
roles: []types.Role{
wildcardRole,
wildcardDenyRole,
},
checks: []impersonate{
{
allowed: false,
user: newUser("bob", empty),
roles: []types.Role{
noLabelsRole,
},
},
},
},
{
name: "impersonate condition is limited to a certain set users and roles",
user: newUser("alice", empty),
roles: []types.Role{
&types.RoleV3{
Metadata: types.Metadata{
Name: "limited",
Namespace: defaults.Namespace,
},
Spec: types.RoleSpecV3{
Allow: types.RoleConditions{
Impersonate: &types.ImpersonateConditions{
Users: []string{"bob"},
Roles: []string{noLabelsRole.GetName()},
},
},
},
},
},
checks: []impersonate{
{
allowed: true,
user: newUser("bob", empty),
roles: []types.Role{
noLabelsRole,
},
},
{
allowed: false,
user: newUser("alice", empty),
roles: []types.Role{
noLabelsRole,
},
},
{
allowed: false,
user: newUser("bob", empty),
roles: []types.Role{
wildcardRole,
},
},
},
},
{
name: "Alice can impersonate any user and role from dev team",
user: newUser("alice", props{traits: map[string][]string{"team": []string{"dev"}}}),
roles: []types.Role{
&types.RoleV3{
Metadata: types.Metadata{
Name: "team-impersonator",
Namespace: defaults.Namespace,
},
Spec: types.RoleSpecV3{
Allow: types.RoleConditions{
Impersonate: &types.ImpersonateConditions{
Users: []string{Wildcard},
Roles: []string{Wildcard},
Where: `equals(user.spec.traits["team"], impersonate_user.spec.traits["team"]) && contains(user.spec.traits["team"], impersonate_role.metadata.labels["team"])`,
},
},
},
},
},
checks: []impersonate{
{
allowed: true,
user: newUser("bob", props{
traits: map[string][]string{"team": []string{"dev"}},
}),
roles: []types.Role{
&types.RoleV3{
Metadata: types.Metadata{
Name: "dev",
Namespace: defaults.Namespace,
Labels: map[string]string{
"team": "dev",
},
},
Spec: types.RoleSpecV3{},
},
},
},
{
name: "all roles in the set have to match where condition",
allowed: false,
user: newUser("bob", props{
traits: map[string][]string{"team": []string{"dev"}},
}),
roles: []types.Role{
wildcardRole,
&types.RoleV3{
Metadata: types.Metadata{
Name: "dev",
Namespace: defaults.Namespace,
Labels: map[string]string{
"team": "dev",
},
},
Spec: types.RoleSpecV3{},
},
},
},
},
},
}
for i, tc := range testCases {
var set RoleSet
for i := range tc.roles {
set = append(set, tc.roles[i])
}
for j, impersonate := range tc.checks {
comment := fmt.Sprintf("test case %v '%v', check %v %v", i, tc.name, j, impersonate.name)
result := set.CheckImpersonate(tc.user, impersonate.user, impersonate.roles)
if impersonate.allowed {
require.NoError(t, result, comment)
} else {
require.True(t, trace.IsAccessDenied(result), fmt.Sprintf("%v: %v", comment, result))
}
}
}
}

View file

@ -188,6 +188,10 @@ const (
UserIdentifier = "user"
// ResourceIdentifier represents resource registered identifier in the rules
ResourceIdentifier = "resource"
// ImpersonateRoleIdentifier is a role to impersonate
ImpersonateRoleIdentifier = "impersonate_role"
// ImpersonateUserIdentifier is a user to impersonate
ImpersonateUserIdentifier = "impersonate_user"
)
// GetResource returns resource specified in the context,

View file

@ -54,6 +54,7 @@ func NewPresetEditorRole() Role {
NewRule(KindClusterConfig, RW()),
NewRule(KindTrustedCluster, RW()),
NewRule(KindRemoteCluster, RW()),
NewRule(KindToken, RW()),
},
},
},

View file

@ -509,6 +509,34 @@ func ApplyTraits(r Role, traits map[string][]string) Role {
if inLabels != nil {
r.SetDatabaseLabels(condition, applyLabelsTraits(inLabels, traits))
}
// apply templates to impersonation conditions
inCond := r.GetImpersonateConditions(condition)
var outCond types.ImpersonateConditions
for _, user := range inCond.Users {
variableValues, err := applyValueTraits(user, traits)
if err != nil {
if !trace.IsNotFound(err) {
log.WithError(err).Debugf("Skipping impersonate user %q.", user)
}
continue
}
outCond.Users = append(outCond.Users, variableValues...)
}
for _, role := range inCond.Roles {
variableValues, err := applyValueTraits(role, traits)
if err != nil {
if !trace.IsNotFound(err) {
log.WithError(err).Debugf("Skipping impersonate role %q.", role)
}
continue
}
outCond.Roles = append(outCond.Roles, variableValues...)
}
outCond.Users = utils.Deduplicate(outCond.Users)
outCond.Roles = utils.Deduplicate(outCond.Roles)
outCond.Where = inCond.Where
r.SetImpersonateConditions(condition, outCond)
}
return r
@ -811,9 +839,17 @@ type AccessChecker interface {
// CheckDatabaseNamesAndUsers returns database names and users this role
// is allowed to use.
CheckDatabaseNamesAndUsers(ttl time.Duration, overrideTTL bool) (names []string, users []string, err error)
// CheckAccessToDatabase checks whether a user has access to the provided
// database server.
CheckAccessToDatabase(server types.DatabaseServer, mfa AccessMFAParams, matchers ...RoleMatcher) error
// CheckImpersonate checks whether current user is allowed to impersonate
// users and roles
CheckImpersonate(currentUser, impersonateUser types.User, impersonateRoles []types.Role) error
// CanImpersonateSomeone returns true if this checker has any impersonation rules
CanImpersonateSomeone() bool
}
// FromSpec returns new RoleSet created from spec
@ -900,10 +936,9 @@ func ExtractFromIdentity(access UserGetter, identity tlsca.Identity) ([]string,
return identity.Groups, identity.Traits, nil
}
// FetchRoles fetches roles by their names, applies the traits to role
// variables, and returns the RoleSet. Adds runtime roles like the default
// implicit role to RoleSet.
func FetchRoles(roleNames []string, access RoleGetter, traits map[string][]string) (RoleSet, error) {
// FetchRoleList fetches roles by their names, applies the traits to role
// variables, and returns the list
func FetchRoleList(roleNames []string, access RoleGetter, traits map[string][]string) (RoleSet, error) {
var roles []Role
for _, roleName := range roleNames {
@ -914,6 +949,17 @@ func FetchRoles(roleNames []string, access RoleGetter, traits map[string][]strin
roles = append(roles, ApplyTraits(role, traits))
}
return roles, nil
}
// FetchRoles fetches roles by their names, applies the traits to role
// variables, and returns the RoleSet. Adds runtime roles like the default
// implicit role to RoleSet.
func FetchRoles(roleNames []string, access RoleGetter, traits map[string][]string) (RoleSet, error) {
roles, err := FetchRoleList(roleNames, access, traits)
if err != nil {
return nil, trace.Wrap(err)
}
return NewRoleSet(roles...), nil
}
@ -1580,6 +1626,148 @@ func (set RoleSet) CheckAccessToKubernetes(namespace string, kube *KubernetesClu
return trace.AccessDenied("access to kubernetes cluster denied")
}
// CanImpersonateSomeone returns true if this checker has any impersonation rules
func (set RoleSet) CanImpersonateSomeone() bool {
for _, role := range set {
cond := role.GetImpersonateConditions(Allow)
if !cond.IsEmpty() {
return true
}
}
return false
}
// CheckImpersonate returns nil if this role set can impersonate
// a user and their roles, returns AccessDenied otherwise
// CheckImpersonate checks whether current user is allowed to impersonate
// users and roles
func (set RoleSet) CheckImpersonate(currentUser, impersonateUser types.User, impersonateRoles []types.Role) error {
ctx := &impersonateContext{
user: currentUser,
impersonateUser: impersonateUser,
}
whereParser, err := newImpersonateWhereParser(ctx)
if err != nil {
return trace.Wrap(err)
}
// check deny: a single match on a deny rule prohibits access
for _, role := range set {
cond := role.GetImpersonateConditions(Deny)
matched, err := matchDenyImpersonateCondition(cond, impersonateUser, impersonateRoles)
if err != nil {
return trace.Wrap(err)
}
if matched {
return trace.AccessDenied("access denied to '%s' to impersonate user '%s' and roles '%s'", currentUser.GetName(), impersonateUser.GetName(), roleNames(impersonateRoles))
}
}
// check allow: if matches, allow to impersonate
for _, role := range set {
cond := role.GetImpersonateConditions(Allow)
matched, err := matchAllowImpersonateCondition(ctx, whereParser, cond, impersonateUser, impersonateRoles)
if err != nil {
return trace.Wrap(err)
}
if matched {
return nil
}
}
return trace.AccessDenied("access denied to '%s' to impersonate user '%s' and roles '%s'", currentUser.GetName(), impersonateUser.GetName(), roleNames(impersonateRoles))
}
func roleNames(roles []types.Role) string {
out := make([]string, len(roles))
for i := range roles {
out[i] = roles[i].GetName()
}
return strings.Join(out, ", ")
}
// matchAllowImpersonateCondition matches impersonate condition,
// both user, role and where condition has to match
func matchAllowImpersonateCondition(ctx *impersonateContext, whereParser predicate.Parser, cond types.ImpersonateConditions, impersonateUser types.User, impersonateRoles []types.Role) (bool, error) {
// an empty set matches nothing
if len(cond.Users) == 0 && len(cond.Roles) == 0 {
return false, nil
}
// should specify both roles and users, this condition is also verified on the role level
if len(cond.Users) == 0 || len(cond.Roles) == 0 {
return false, trace.BadParameter("the system does not support empty roles and users")
}
anyUser, err := parse.NewAnyMatcher(cond.Users)
if err != nil {
return false, trace.Wrap(err)
}
if !anyUser.Match(impersonateUser.GetName()) {
return false, nil
}
anyRole, err := parse.NewAnyMatcher(cond.Roles)
if err != nil {
return false, trace.Wrap(err)
}
for _, impersonateRole := range impersonateRoles {
if !anyRole.Match(impersonateRole.GetName()) {
return false, nil
}
// TODO:
// This set impersonateRole inside the ctx that is in turn used inside whereParser
// which is created in CheckImpersonate above but is being used right below.
// This is unfortunate interface of the parser, instead
// parser should accept additional context as a first argument.
ctx.impersonateRole = impersonateRole
match, err := matchesImpersonateWhere(cond, whereParser)
if err != nil {
return false, trace.Wrap(err)
}
if !match {
return false, nil
}
}
return true, nil
}
// matchDenyImpersonateCondition matches impersonate condition,
// greedy is used for deny type rules, where any user or role can match
func matchDenyImpersonateCondition(cond types.ImpersonateConditions, impersonateUser types.User, impersonateRoles []types.Role) (bool, error) {
// an empty set matches nothing
if len(cond.Users) == 0 && len(cond.Roles) == 0 {
return false, nil
}
// should specify both roles and users, this condition is also verified on the role level
if len(cond.Users) == 0 || len(cond.Roles) == 0 {
return false, trace.BadParameter("the system does not support empty roles and users")
}
anyUser, err := parse.NewAnyMatcher(cond.Users)
if err != nil {
return false, trace.Wrap(err)
}
if anyUser.Match(impersonateUser.GetName()) {
return true, nil
}
anyRole, err := parse.NewAnyMatcher(cond.Roles)
if err != nil {
return false, trace.Wrap(err)
}
for _, impersonateRole := range impersonateRoles {
if anyRole.Match(impersonateRole.GetName()) {
return true, nil
}
}
return false, nil
}
// RoleMatcher defines an interface for a generic role matcher.
type RoleMatcher interface {
Match(Role, RoleConditionType) (bool, error)
@ -2029,6 +2217,23 @@ const RoleSpecV3SchemaDefinitions = `
}
}
},
"impersonate": {
"type": "object",
"additionalProperties": false,
"properties": {
"users": {
"type": "array",
"items": { "type": "string" }
},
"roles": {
"type": "array",
"items": { "type": "string" }
},
"where": {
"type": "string"
}
}
},
"rules": {
"type": "array",
"items": {

View file

@ -1,5 +1,5 @@
/*
Copyright 2015-2020 Gravitational, Inc.
Copyright 2015-2021 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -1500,24 +1500,26 @@ func TestCheckRuleSorting(t *testing.T) {
func TestApplyTraits(t *testing.T) {
type rule struct {
inLogins []string
outLogins []string
inLabels Labels
outLabels Labels
inKubeLabels Labels
outKubeLabels Labels
inKubeGroups []string
outKubeGroups []string
inKubeUsers []string
outKubeUsers []string
inAppLabels Labels
outAppLabels Labels
inDBLabels Labels
outDBLabels Labels
inDBNames []string
outDBNames []string
inDBUsers []string
outDBUsers []string
inLogins []string
outLogins []string
inLabels Labels
outLabels Labels
inKubeLabels Labels
outKubeLabels Labels
inKubeGroups []string
outKubeGroups []string
inKubeUsers []string
outKubeUsers []string
inAppLabels Labels
outAppLabels Labels
inDBLabels Labels
outDBLabels Labels
inDBNames []string
outDBNames []string
inDBUsers []string
outDBUsers []string
inImpersonate types.ImpersonateConditions
outImpersonate types.ImpersonateConditions
}
var tests = []struct {
comment string
@ -1818,6 +1820,38 @@ func TestApplyTraits(t *testing.T) {
outDBLabels: Labels{`key`: []string{"bar", "baz"}},
},
},
{
comment: "impersonate roles",
inTraits: map[string][]string{
"teams": {"devs"},
"users": {"alice", "bob"},
"blocked_users": {"root"},
"blocked_teams": {"admins"},
},
allow: rule{
inImpersonate: types.ImpersonateConditions{
Users: []string{"{{external.users}}"},
Roles: []string{"{{external.teams}}"},
Where: `contains(user.spec.traits, "hello")`,
},
outImpersonate: types.ImpersonateConditions{
Users: []string{"alice", "bob"},
Roles: []string{"devs"},
Where: `contains(user.spec.traits, "hello")`,
},
},
deny: rule{
inImpersonate: types.ImpersonateConditions{
Users: []string{"{{external.blocked_users}}"},
Roles: []string{"{{external.blocked_teams}}"},
},
outImpersonate: types.ImpersonateConditions{
Users: []string{"root"},
Roles: []string{"admins"},
},
},
},
}
for i, tt := range tests {
@ -1842,6 +1876,7 @@ func TestApplyTraits(t *testing.T) {
DatabaseLabels: tt.allow.inDBLabels,
DatabaseNames: tt.allow.inDBNames,
DatabaseUsers: tt.allow.inDBUsers,
Impersonate: &tt.allow.inImpersonate,
},
Deny: RoleConditions{
Logins: tt.deny.inLogins,
@ -1854,6 +1889,7 @@ func TestApplyTraits(t *testing.T) {
DatabaseLabels: tt.deny.inDBLabels,
DatabaseNames: tt.deny.inDBNames,
DatabaseUsers: tt.deny.inDBUsers,
Impersonate: &tt.deny.inImpersonate,
},
},
}
@ -1869,6 +1905,7 @@ func TestApplyTraits(t *testing.T) {
require.Equal(t, outRole.GetDatabaseLabels(Allow), tt.allow.outDBLabels, comment)
require.Equal(t, outRole.GetDatabaseNames(Allow), tt.allow.outDBNames, comment)
require.Equal(t, outRole.GetDatabaseUsers(Allow), tt.allow.outDBUsers, comment)
require.Equal(t, outRole.GetImpersonateConditions(Allow), tt.allow.outImpersonate, comment)
require.Equal(t, outRole.GetLogins(Deny), tt.deny.outLogins, comment)
require.Equal(t, outRole.GetNodeLabels(Deny), tt.deny.outLabels, comment)
@ -1880,6 +1917,7 @@ func TestApplyTraits(t *testing.T) {
require.Equal(t, outRole.GetDatabaseLabels(Deny), tt.deny.outDBLabels, comment)
require.Equal(t, outRole.GetDatabaseNames(Deny), tt.deny.outDBNames, comment)
require.Equal(t, outRole.GetDatabaseUsers(Deny), tt.deny.outDBUsers, comment)
require.Equal(t, outRole.GetImpersonateConditions(Deny), tt.deny.outImpersonate, comment)
}
}

View file

@ -151,7 +151,8 @@ func (s *Server) newStreamWriter(identity *tlsca.Identity) (events.StreamWriter,
WithMFA: identity.MFAVerified,
},
UserMetadata: events.UserMetadata{
User: identity.Username,
User: identity.Username,
Impersonator: identity.Impersonator,
},
SessionChunkID: chunkID,
}

View file

@ -97,6 +97,7 @@ func (h *AuthHandlers) CreateIdentityContext(sconn *ssh.ServerConn) (IdentityCon
return IdentityContext{}, trace.Wrap(err)
}
identity.RoleSet = roleSet
identity.Impersonator = certificate.Extensions[teleport.CertExtensionImpersonator]
return identity, nil
}
@ -123,8 +124,9 @@ func (h *AuthHandlers) CheckPortForward(addr string, ctx *ServerContext) error {
Code: events.PortForwardFailureCode,
},
UserMetadata: events.UserMetadata{
Login: ctx.Identity.Login,
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
User: ctx.Identity.TeleportUser,
Impersonator: ctx.Identity.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: ctx.ServerConn.LocalAddr().String(),

View file

@ -132,6 +132,9 @@ type IdentityContext struct {
// TeleportUser is the Teleport user associated with the connection.
TeleportUser string
// Impersonator is a user acting on behalf of other user
Impersonator string
// Login is the operating system user associated with the connection.
Login string
@ -569,8 +572,9 @@ func (c *ServerContext) reportStats(conn utils.Stater) {
WithMFA: c.Identity.Certificate.Extensions[teleport.CertExtensionMFAVerified],
},
UserMetadata: events.UserMetadata{
User: c.Identity.TeleportUser,
Login: c.Identity.Login,
User: c.Identity.TeleportUser,
Login: c.Identity.Login,
Impersonator: c.Identity.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
RemoteAddr: c.ServerConn.RemoteAddr().String(),

View file

@ -69,7 +69,8 @@ func (a *Audit) OnSessionStart(ctx context.Context, session Session, sessionErr
ServerNamespace: defaults.Namespace,
},
UserMetadata: events.UserMetadata{
User: session.Identity.Username,
User: session.Identity.Username,
Impersonator: session.Identity.Impersonator,
},
SessionMetadata: events.SessionMetadata{
SessionID: session.ID,
@ -112,7 +113,8 @@ func (a *Audit) OnSessionEnd(ctx context.Context, session Session) error {
ClusterName: session.ClusterName,
},
UserMetadata: events.UserMetadata{
User: session.Identity.Username,
User: session.Identity.Username,
Impersonator: session.Identity.Impersonator,
},
SessionMetadata: events.SessionMetadata{
SessionID: session.ID,
@ -141,7 +143,8 @@ func (a *Audit) OnQuery(ctx context.Context, session Session, query string) erro
ClusterName: session.ClusterName,
},
UserMetadata: events.UserMetadata{
User: session.Identity.Username,
User: session.Identity.Username,
Impersonator: session.Identity.Impersonator,
},
SessionMetadata: events.SessionMetadata{
SessionID: session.ID,

View file

@ -372,8 +372,9 @@ func emitExecAuditEvent(ctx *ServerContext, cmd string, execErr error) {
}
userMeta := events.UserMetadata{
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
Impersonator: ctx.Identity.Impersonator,
}
connectionMeta := events.ConnectionMetadata{

View file

@ -712,8 +712,9 @@ func (s *Server) handleDirectTCPIPRequest(ctx context.Context, ch ssh.Channel, r
Code: events.PortForwardCode,
},
UserMetadata: events.UserMetadata{
Login: s.identityContext.Login,
User: s.identityContext.TeleportUser,
Login: s.identityContext.Login,
User: s.identityContext.TeleportUser,
Impersonator: s.identityContext.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: s.sconn.LocalAddr().String(),
@ -1049,8 +1050,9 @@ func (s *Server) handleX11Forward(ctx context.Context, ch ssh.Channel, req *ssh.
Type: events.X11ForwardEvent,
},
UserMetadata: events.UserMetadata{
Login: s.identityContext.Login,
User: s.identityContext.TeleportUser,
Login: s.identityContext.Login,
User: s.identityContext.TeleportUser,
Impersonator: s.identityContext.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: s.sconn.LocalAddr().String(),

View file

@ -136,8 +136,9 @@ func (r *remoteSubsystem) emitAuditEvent(err error) {
Type: events.SubsystemEvent,
},
UserMetadata: events.UserMetadata{
User: r.serverContext.Identity.TeleportUser,
Login: r.serverContext.Identity.Login,
User: r.serverContext.Identity.TeleportUser,
Login: r.serverContext.Identity.Login,
Impersonator: r.serverContext.Identity.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: r.serverContext.RemoteClient.LocalAddr().String(),

View file

@ -886,8 +886,9 @@ func (s *Server) HandleNewConn(ctx context.Context, ccx *sshutils.ConnectionCont
Code: events.SessionRejectedCode,
},
UserMetadata: events.UserMetadata{
Login: identityContext.Login,
User: identityContext.TeleportUser,
Login: identityContext.Login,
User: identityContext.TeleportUser,
Impersonator: identityContext.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
Protocol: events.EventProtocolSSH,
@ -985,8 +986,9 @@ func (s *Server) HandleNewChan(ctx context.Context, ccx *sshutils.ConnectionCont
Code: events.SessionRejectedCode,
},
UserMetadata: events.UserMetadata{
Login: identityContext.Login,
User: identityContext.TeleportUser,
Login: identityContext.Login,
User: identityContext.TeleportUser,
Impersonator: identityContext.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
Protocol: events.EventProtocolSSH,
@ -1152,8 +1154,9 @@ Loop:
Code: events.PortForwardCode,
},
UserMetadata: events.UserMetadata{
Login: scx.Identity.Login,
User: scx.Identity.TeleportUser,
Login: scx.Identity.Login,
User: scx.Identity.TeleportUser,
Impersonator: scx.Identity.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: scx.ServerConn.LocalAddr().String(),

View file

@ -141,8 +141,9 @@ func (s *SessionRegistry) emitSessionJoinEvent(ctx *ServerContext) {
SessionID: string(ctx.SessionID()),
},
UserMetadata: events.UserMetadata{
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
Impersonator: ctx.Identity.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
RemoteAddr: ctx.ServerConn.RemoteAddr().String(),
@ -409,8 +410,9 @@ func (s *SessionRegistry) NotifyWinChange(params rsession.TerminalParams, ctx *S
SessionID: string(sid),
},
UserMetadata: events.UserMetadata{
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
Impersonator: ctx.Identity.Impersonator,
},
TerminalSize: params.Serialize(),
}
@ -754,8 +756,9 @@ func (s *session) startInteractive(ch ssh.Channel, ctx *ServerContext) error {
SessionID: string(s.id),
},
UserMetadata: events.UserMetadata{
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
Impersonator: ctx.Identity.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
RemoteAddr: ctx.ServerConn.RemoteAddr().String(),
@ -897,8 +900,9 @@ func (s *session) startExec(channel ssh.Channel, ctx *ServerContext) error {
SessionID: string(s.id),
},
UserMetadata: events.UserMetadata{
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
Impersonator: ctx.Identity.Impersonator,
},
ConnectionMetadata: events.ConnectionMetadata{
RemoteAddr: ctx.ServerConn.RemoteAddr().String(),
@ -992,8 +996,9 @@ func (s *session) startExec(channel ssh.Channel, ctx *ServerContext) error {
SessionID: string(s.id),
},
UserMetadata: events.UserMetadata{
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
User: ctx.Identity.TeleportUser,
Login: ctx.Identity.Login,
Impersonator: ctx.Identity.Impersonator,
},
EnhancedRecording: s.hasEnhancedRecording,
Interactive: false,

View file

@ -80,6 +80,8 @@ type CertAuthority struct {
type Identity struct {
// Username is a username or name of the node connection
Username string
// Impersonator is a username of a user impersonating this user
Impersonator string
// Groups is a list of groups (Teleport roles) encoded in the identity
Groups []string
// Usage is a list of usage restrictions encoded in the identity
@ -252,6 +254,10 @@ var (
// DatabaseUsersASN1ExtensionOID is an extension OID used when encoding/decoding
// allowed database users into certificates.
DatabaseUsersASN1ExtensionOID = asn1.ObjectIdentifier{1, 3, 9999, 2, 6}
// ImpersonatorASN1ExtensionOID is an extension OID used when encoding/decoding
// impersonator user
ImpersonatorASN1ExtensionOID = asn1.ObjectIdentifier{1, 3, 9999, 2, 7}
)
// Subject converts identity to X.509 subject name
@ -394,6 +400,14 @@ func (id *Identity) Subject() (pkix.Name, error) {
})
}
if id.Impersonator != "" {
subject.ExtraNames = append(subject.ExtraNames,
pkix.AttributeTypeAndValue{
Type: ImpersonatorASN1ExtensionOID,
Value: id.Impersonator,
})
}
return subject, nil
}
@ -493,6 +507,11 @@ func FromSubject(subject pkix.Name, expires time.Time) (*Identity, error) {
if ok {
id.DatabaseUsers = append(id.DatabaseUsers, val)
}
case attr.Type.Equal(ImpersonatorASN1ExtensionOID):
val, ok := attr.Value.(string)
if ok {
id.Impersonator = val
}
}
}

View file

@ -76,8 +76,9 @@ func TestKubeExtensions(t *testing.T) {
expires := clock.Now().Add(time.Hour)
identity := Identity{
Username: "alice@example.com",
Groups: []string{"admin"},
Username: "alice@example.com",
Groups: []string{"admin"},
Impersonator: "bob@example.com",
// Generate a certificate restricted for
// use against a kubernetes endpoint, and not the API server endpoint
// otherwise proxies can generate certs for any user.

View file

@ -167,6 +167,35 @@ type Matcher interface {
Match(in string) bool
}
// MatcherFn converts function to a matcher interface
type MatcherFn func(in string) bool
// Match matches string against a regexp
func (fn MatcherFn) Match(in string) bool {
return fn(in)
}
// NewAnyMatcher returns a matcher function based
// on incoming values
func NewAnyMatcher(in []string) (Matcher, error) {
matchers := make([]Matcher, len(in))
for i, v := range in {
m, err := NewMatcher(v)
if err != nil {
return nil, trace.Wrap(err)
}
matchers[i] = m
}
return MatcherFn(func(in string) bool {
for _, m := range matchers {
if m.Match(in) {
return true
}
}
return false
}), nil
}
// NewMatcher parses a matcher expression. Currently supported expressions:
// - string literal: `foo`
// - wildcard expression: `*` or `foo*bar`