refactor (controller): Use a grpc server for the grpc-gateway (#1576)

* refactor (controller): Use a grpc server for the grpc-gateway

This starts a grpc server for the grpc-gateway.  The new gateway
server uses an in-memory listener which also supports "dialing", so
the http proxy can connect to the in-memory listener.

This change allows us to use all grpc server features and was in part
motivated by a need for an interceptor of inbound requests.  With that
said, having a "proper" grpc server for the gateway will provide a lot
more flexibility and capabilities moving forward.

The request context will still be verified via middleware in the
http proxy (see wrapHandlerWithCommonFuncs).  This verified context
will be serialized into a protobuf and sent along to the grpc-gateway
via metadata where an interceptor will add the verified request
info to the request's context (see requestCtxInterceptor).  The
interceptor will verify the serialized request info via a shared ticket
with it's companion in-memory http proxy.  FYI: the ticket is simply
random base62 string (see db.NewPrivateId).

This change also necessitated a minor change to services.  We need to
return an empty response instead of nil, nil when there are no errors
and the response is empty.

* refactor (handlers): Rename gRPC Gateway filter

Originally, this was called an OutgoingInterceptor, which is an
unfortunate name since it's not an interceptor.  Now that we're
refactoring the gateway stuff and have proper interceptors, renaming
this should remove ambiguity

* refactor: Rework how 204 with empty msg is handled

Recent changes to grpc gateway has necessitated a change to how
204s are handled by the gateway

* tests (bats): Check status codes for delete operations
This commit is contained in:
Jim authored and GitHub committed 2021-10-19 17:21:41 -04:00
1 parent 55ba24499a
commit 769416c2ff
50 files changed
+2625 -370

No files matched your search

+2
View File
@@ -13,10 +13,12 @@ require (
github.com/fatih/color v1.12.0
github.com/fatih/structs v1.1.0
github.com/favadi/protoc-go-inject-tag v1.3.0
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/golang-migrate/migrate/v4 v4.14.1
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe
github.com/golang/protobuf v1.5.2
github.com/google/go-cmp v0.5.6
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0
github.com/hashicorp/boundary/api v0.0.19
github.com/hashicorp/boundary/sdk v0.0.11
+5 -1
View File
@@ -322,8 +322,9 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY=
github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@@ -432,6 +433,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
@@ -1407,6 +1410,7 @@ google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200323114720-3f67cca34472/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/boundary/api/authtokens"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/authtoken"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/servers"
"github.com/hashicorp/boundary/internal/servers/controller"
@@ -92,10 +93,10 @@ func TestFetchActionSetForId(t *testing.T) {
authTokenRepoFn,
serversRepoFn,
tc.Kms(),
auth.RequestInfo{
&authpb.RequestInfo{
PublicId: token.Id,
EncryptedToken: strings.Split(token.Token, "_")[2],
TokenFormat: auth.AuthTokenTypeBearer,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
})
typ := resource.Target
if tt.typeOverride != resource.Unknown {
+5 -3
View File
@@ -66,9 +66,11 @@ const (
GenKey Code = 304 // GenKey represents an error occurred during the underlying key generation process
GenCert Code = 305 // GenCert represents an error occurred during the underlying certificate generation process
// General system errors are reserved Codes 400-599 and align with http client and server error codes
Forbidden Code = 403 // Forbidden represents the operation is forbidden
Internal Code = 500 // InternalError represents the system encountered an unexpected condition.
// General system errors are reserved Codes 400-599 and align with http
// client and server error codes
Unauthorized Code = 401 // Unauthorized represents the operation is unauthorized
Forbidden Code = 403 // Forbidden represents the operation is forbidden
Internal Code = 500 // InternalError represents the system encountered an unexpected condition.
// DB errors are reserved Codes from 1000-1999
CheckConstraint Code = 1000 // CheckConstraint represents a check constraint error
+44
View File
@@ -7,6 +7,7 @@ import (
"runtime"
"strings"
pberrors "github.com/hashicorp/boundary/internal/gen/errors"
"github.com/hashicorp/boundary/internal/observability/event"
"github.com/jackc/pgconn"
)
@@ -266,6 +267,49 @@ func (e *Err) Error() string {
return s.String()
}
// ToPbErrors will convert to an Err protobuf
func ToPbErrors(err *Err) *pberrors.Err {
pbErr := &pberrors.Err{
Code: uint32(err.Code),
Msg: err.Msg,
Op: string(err.Op),
}
var wrappedErr *Err
isWrappedErr := As(err.Wrapped, &wrappedErr)
switch {
case err.Wrapped == nil:
pbErr.Wrapped = &pberrors.Err_None{
None: false,
}
case isWrappedErr:
pbErr.Wrapped = &pberrors.Err_Err{
Err: ToPbErrors(wrappedErr),
}
default:
pbErr.Wrapped = &pberrors.Err_StdError{
StdError: err.Wrapped.Error(),
}
}
return pbErr
}
// FromPbErrors will convert from Err protobuf
func FromPbErrors(pbErr *pberrors.Err) *Err {
err := &Err{
Code: Code(pbErr.Code),
Msg: pbErr.Msg,
Op: Op(pbErr.Op),
}
switch w := pbErr.Wrapped.(type) {
case *pberrors.Err_Err:
err.Wrapped = FromPbErrors(w.Err)
case *pberrors.Err_StdError:
err.Wrapped = errors.New(w.StdError)
}
return err
}
func join(str *strings.Builder, delim string, s string) {
if str.Len() == 0 {
_, _ = str.WriteString(s)
+4
View File
@@ -148,6 +148,10 @@ var errorCodeInfo = map[Code]Info{
Message: "forbidden",
Kind: Other,
},
Unauthorized: {
Message: "unauthorized",
Kind: Other,
},
CheckConstraint: {
Message: "constraint check failed",
Kind: Integrity,
+137
View File
@@ -0,0 +1,137 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// source: controller/api/v1/empty_msg.proto
package api
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// EmptyResponse represents an empty response
type EmptyResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *EmptyResponse) Reset() {
*x = EmptyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_api_v1_empty_msg_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EmptyResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EmptyResponse) ProtoMessage() {}
func (x *EmptyResponse) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_v1_empty_msg_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EmptyResponse.ProtoReflect.Descriptor instead.
func (*EmptyResponse) Descriptor() ([]byte, []int) {
return file_controller_api_v1_empty_msg_proto_rawDescGZIP(), []int{0}
}
var File_controller_api_v1_empty_msg_proto protoreflect.FileDescriptor
var file_controller_api_v1_empty_msg_proto_rawDesc = []byte{
0x0a, 0x21, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x6d, 0x73, 0x67, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x22, 0x0f, 0x0a, 0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f,
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2f, 0x61, 0x70, 0x69, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_controller_api_v1_empty_msg_proto_rawDescOnce sync.Once
file_controller_api_v1_empty_msg_proto_rawDescData = file_controller_api_v1_empty_msg_proto_rawDesc
)
func file_controller_api_v1_empty_msg_proto_rawDescGZIP() []byte {
file_controller_api_v1_empty_msg_proto_rawDescOnce.Do(func() {
file_controller_api_v1_empty_msg_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_v1_empty_msg_proto_rawDescData)
})
return file_controller_api_v1_empty_msg_proto_rawDescData
}
var file_controller_api_v1_empty_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_controller_api_v1_empty_msg_proto_goTypes = []interface{}{
(*EmptyResponse)(nil), // 0: controller.api.v1.EmptyResponse
}
var file_controller_api_v1_empty_msg_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_controller_api_v1_empty_msg_proto_init() }
func file_controller_api_v1_empty_msg_proto_init() {
if File_controller_api_v1_empty_msg_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_controller_api_v1_empty_msg_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EmptyResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_api_v1_empty_msg_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_api_v1_empty_msg_proto_goTypes,
DependencyIndexes: file_controller_api_v1_empty_msg_proto_depIdxs,
MessageInfos: file_controller_api_v1_empty_msg_proto_msgTypes,
}.Build()
File_controller_api_v1_empty_msg_proto = out.File
file_controller_api_v1_empty_msg_proto_rawDesc = nil
file_controller_api_v1_empty_msg_proto_goTypes = nil
file_controller_api_v1_empty_msg_proto_depIdxs = nil
}
+263
View File
@@ -0,0 +1,263 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// source: controller/auth/v1/auth.proto
package auth
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// RequestInfo contains request parameters necessary for checking authn/authz
type RequestInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// path of the request
Path string `protobuf:"bytes,10,opt,name=path,proto3" json:"path,omitempty"`
// method (http verb) of the request
Method string `protobuf:"bytes,20,opt,name=method,proto3" json:"method,omitempty"`
// public_id for the request
PublicId string `protobuf:"bytes,30,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty"`
// encrypted_token from the request
EncryptedToken string `protobuf:"bytes,40,opt,name=encrypted_token,json=encryptedToken,proto3" json:"encrypted_token,omitempty"`
// token from the request
Token string `protobuf:"bytes,50,opt,name=token,proto3" json:"token,omitempty"`
// token_format of the request's token
TokenFormat uint32 `protobuf:"varint,60,opt,name=token_format,json=tokenFormat,proto3" json:"token_format,omitempty"`
// scope_id_override for the request (helpful for tests)
ScopeIdOverride string `protobuf:"bytes,70,opt,name=scope_id_override,json=scopeIdOverride,proto3" json:"scope_id_override,omitempty"`
// user_id_override for the request (helpful for tests)
UserIdOverride string `protobuf:"bytes,80,opt,name=user_id_override,json=userIdOverride,proto3" json:"user_id_override,omitempty"`
// disable_authz_failures for the request (helpful for tests)
DisableAuthzFailures bool `protobuf:"varint,90,opt,name=disable_authz_failures,json=disableAuthzFailures,proto3" json:"disable_authz_failures,omitempty"`
// disable_auth_entirely for the request (helpful for tests)
DisableAuthEntirely bool `protobuf:"varint,100,opt,name=disable_auth_entirely,json=disableAuthEntirely,proto3" json:"disable_auth_entirely,omitempty"`
// ticket is a unique id that allows the grpc-gateway to verify that the info
// came from its companion http proxy
Ticket string `protobuf:"bytes,110,opt,name=ticket,proto3" json:"ticket,omitempty"`
}
func (x *RequestInfo) Reset() {
*x = RequestInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_auth_v1_auth_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RequestInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RequestInfo) ProtoMessage() {}
func (x *RequestInfo) ProtoReflect() protoreflect.Message {
mi := &file_controller_auth_v1_auth_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RequestInfo.ProtoReflect.Descriptor instead.
func (*RequestInfo) Descriptor() ([]byte, []int) {
return file_controller_auth_v1_auth_proto_rawDescGZIP(), []int{0}
}
func (x *RequestInfo) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *RequestInfo) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (x *RequestInfo) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *RequestInfo) GetEncryptedToken() string {
if x != nil {
return x.EncryptedToken
}
return ""
}
func (x *RequestInfo) GetToken() string {
if x != nil {
return x.Token
}
return ""
}
func (x *RequestInfo) GetTokenFormat() uint32 {
if x != nil {
return x.TokenFormat
}
return 0
}
func (x *RequestInfo) GetScopeIdOverride() string {
if x != nil {
return x.ScopeIdOverride
}
return ""
}
func (x *RequestInfo) GetUserIdOverride() string {
if x != nil {
return x.UserIdOverride
}
return ""
}
func (x *RequestInfo) GetDisableAuthzFailures() bool {
if x != nil {
return x.DisableAuthzFailures
}
return false
}
func (x *RequestInfo) GetDisableAuthEntirely() bool {
if x != nil {
return x.DisableAuthEntirely
}
return false
}
func (x *RequestInfo) GetTicket() string {
if x != nil {
return x.Ticket
}
return ""
}
var File_controller_auth_v1_auth_proto protoreflect.FileDescriptor
var file_controller_auth_v1_auth_proto_rawDesc = []byte{
0x0a, 0x1d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x75, 0x74,
0x68, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x12, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x75, 0x74, 0x68,
0x2e, 0x76, 0x31, 0x22, 0x90, 0x03, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49,
0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,
0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f,
0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x32,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x74,
0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x3c, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x0b, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x2a,
0x0a, 0x11, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72,
0x69, 0x64, 0x65, 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x49, 0x64, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73,
0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x50,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x4f, 0x76, 0x65, 0x72,
0x72, 0x69, 0x64, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f,
0x61, 0x75, 0x74, 0x68, 0x7a, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x18, 0x5a,
0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74,
0x68, 0x7a, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x69,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x72,
0x65, 0x6c, 0x79, 0x18, 0x64, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62,
0x6c, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x6c, 0x79, 0x12, 0x16,
0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x42, 0x41, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62,
0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f,
0x61, 0x75, 0x74, 0x68, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_controller_auth_v1_auth_proto_rawDescOnce sync.Once
file_controller_auth_v1_auth_proto_rawDescData = file_controller_auth_v1_auth_proto_rawDesc
)
func file_controller_auth_v1_auth_proto_rawDescGZIP() []byte {
file_controller_auth_v1_auth_proto_rawDescOnce.Do(func() {
file_controller_auth_v1_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_auth_v1_auth_proto_rawDescData)
})
return file_controller_auth_v1_auth_proto_rawDescData
}
var file_controller_auth_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_controller_auth_v1_auth_proto_goTypes = []interface{}{
(*RequestInfo)(nil), // 0: controller.auth.v1.RequestInfo
}
var file_controller_auth_v1_auth_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_controller_auth_v1_auth_proto_init() }
func file_controller_auth_v1_auth_proto_init() {
if File_controller_auth_v1_auth_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_controller_auth_v1_auth_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RequestInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_auth_v1_auth_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_controller_auth_v1_auth_proto_goTypes,
DependencyIndexes: file_controller_auth_v1_auth_proto_depIdxs,
MessageInfos: file_controller_auth_v1_auth_proto_msgTypes,
}.Build()
File_controller_auth_v1_auth_proto = out.File
file_controller_auth_v1_auth_proto_rawDesc = nil
file_controller_auth_v1_auth_proto_goTypes = nil
file_controller_auth_v1_auth_proto_depIdxs = nil
}
+322
View File
@@ -0,0 +1,322 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// source: errors/v1/errors.proto
package errors
import (
api "github.com/hashicorp/boundary/internal/gen/controller/api"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Err is a protobuf representation of a domain error
type Err struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// code is the error's code, which can be used to get the error's
// errorCodeInfo, which contains the error's Kind and Message
Code uint32 `protobuf:"varint,10,opt,name=code,proto3" json:"code,omitempty"`
// msg for the error
Msg string `protobuf:"bytes,20,opt,name=msg,proto3" json:"msg,omitempty"`
// op represents the operation raising/propagating an error and is optional.
Op string `protobuf:"bytes,30,opt,name=op,proto3" json:"op,omitempty"`
// wrapped represents an optionally wrapped error
//
// Types that are assignable to Wrapped:
// *Err_Err
// *Err_StdError
// *Err_None
Wrapped isErr_Wrapped `protobuf_oneof:"wrapped"`
}
func (x *Err) Reset() {
*x = Err{}
if protoimpl.UnsafeEnabled {
mi := &file_errors_v1_errors_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Err) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Err) ProtoMessage() {}
func (x *Err) ProtoReflect() protoreflect.Message {
mi := &file_errors_v1_errors_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Err.ProtoReflect.Descriptor instead.
func (*Err) Descriptor() ([]byte, []int) {
return file_errors_v1_errors_proto_rawDescGZIP(), []int{0}
}
func (x *Err) GetCode() uint32 {
if x != nil {
return x.Code
}
return 0
}
func (x *Err) GetMsg() string {
if x != nil {
return x.Msg
}
return ""
}
func (x *Err) GetOp() string {
if x != nil {
return x.Op
}
return ""
}
func (m *Err) GetWrapped() isErr_Wrapped {
if m != nil {
return m.Wrapped
}
return nil
}
func (x *Err) GetErr() *Err {
if x, ok := x.GetWrapped().(*Err_Err); ok {
return x.Err
}
return nil
}
func (x *Err) GetStdError() string {
if x, ok := x.GetWrapped().(*Err_StdError); ok {
return x.StdError
}
return ""
}
func (x *Err) GetNone() bool {
if x, ok := x.GetWrapped().(*Err_None); ok {
return x.None
}
return false
}
type isErr_Wrapped interface {
isErr_Wrapped()
}
type Err_Err struct {
// err is a wrapped Err (aka a domain error)
Err *Err `protobuf:"bytes,40,opt,name=err,proto3,oneof"`
}
type Err_StdError struct {
// std_error is a wrapped std error
StdError string `protobuf:"bytes,50,opt,name=std_error,json=stdError,proto3,oneof"`
}
type Err_None struct {
// no error or err is wrapped
None bool `protobuf:"varint,60,opt,name=none,proto3,oneof"`
}
func (*Err_Err) isErr_Wrapped() {}
func (*Err_StdError) isErr_Wrapped() {}
func (*Err_None) isErr_Wrapped() {}
// ApiError is a protobuf representation of a controller ApiError
type ApiError struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// api_error is the controller ApiError
ApiError *api.Error `protobuf:"bytes,10,opt,name=api_error,json=apiError,proto3" json:"api_error,omitempty"`
// status is the status code assocatiated with the api error
Status int32 `protobuf:"varint,20,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *ApiError) Reset() {
*x = ApiError{}
if protoimpl.UnsafeEnabled {
mi := &file_errors_v1_errors_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ApiError) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ApiError) ProtoMessage() {}
func (x *ApiError) ProtoReflect() protoreflect.Message {
mi := &file_errors_v1_errors_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ApiError.ProtoReflect.Descriptor instead.
func (*ApiError) Descriptor() ([]byte, []int) {
return file_errors_v1_errors_proto_rawDescGZIP(), []int{1}
}
func (x *ApiError) GetApiError() *api.Error {
if x != nil {
return x.ApiError
}
return nil
}
func (x *ApiError) GetStatus() int32 {
if x != nil {
return x.Status
}
return 0
}
var File_errors_v1_errors_proto protoreflect.FileDescriptor
var file_errors_v1_errors_proto_rawDesc = []byte{
0x0a, 0x16, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73,
0x2e, 0x76, 0x31, 0x1a, 0x1d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x22, 0x9f, 0x01, 0x0a, 0x03, 0x45, 0x72, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f,
0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10,
0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67,
0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x70, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x70,
0x12, 0x22, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x72, 0x72, 0x48, 0x00, 0x52,
0x03, 0x65, 0x72, 0x72, 0x12, 0x1d, 0x0a, 0x09, 0x73, 0x74, 0x64, 0x5f, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x73, 0x74, 0x64, 0x45, 0x72,
0x72, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28,
0x08, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x77, 0x72, 0x61,
0x70, 0x70, 0x65, 0x64, 0x22, 0x59, 0x0a, 0x08, 0x41, 0x70, 0x69, 0x45, 0x72, 0x72, 0x6f, 0x72,
0x12, 0x35, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x08, 0x61,
0x70, 0x69, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42,
0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61,
0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79,
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x73, 0x3b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (
file_errors_v1_errors_proto_rawDescOnce sync.Once
file_errors_v1_errors_proto_rawDescData = file_errors_v1_errors_proto_rawDesc
)
func file_errors_v1_errors_proto_rawDescGZIP() []byte {
file_errors_v1_errors_proto_rawDescOnce.Do(func() {
file_errors_v1_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_errors_v1_errors_proto_rawDescData)
})
return file_errors_v1_errors_proto_rawDescData
}
var file_errors_v1_errors_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_errors_v1_errors_proto_goTypes = []interface{}{
(*Err)(nil), // 0: errors.v1.Err
(*ApiError)(nil), // 1: errors.v1.ApiError
(*api.Error)(nil), // 2: controller.api.v1.Error
}
var file_errors_v1_errors_proto_depIdxs = []int32{
0, // 0: errors.v1.Err.err:type_name -> errors.v1.Err
2, // 1: errors.v1.ApiError.api_error:type_name -> controller.api.v1.Error
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_errors_v1_errors_proto_init() }
func file_errors_v1_errors_proto_init() {
if File_errors_v1_errors_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_errors_v1_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Err); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_errors_v1_errors_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ApiError); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_errors_v1_errors_proto_msgTypes[0].OneofWrappers = []interface{}{
(*Err_Err)(nil),
(*Err_StdError)(nil),
(*Err_None)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_errors_v1_errors_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_errors_v1_errors_proto_goTypes,
DependencyIndexes: file_errors_v1_errors_proto_depIdxs,
MessageInfos: file_errors_v1_errors_proto_msgTypes,
}.Build()
File_errors_v1_errors_proto = out.File
file_errors_v1_errors_proto_rawDesc = nil
file_errors_v1_errors_proto_goTypes = nil
file_errors_v1_errors_proto_depIdxs = nil
}
@@ -0,0 +1,222 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// source: testing/interceptor/v1/greeter.proto
package interceptor
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// The request message containing the user's name.
type SayHelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *SayHelloRequest) Reset() {
*x = SayHelloRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_testing_interceptor_v1_greeter_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SayHelloRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SayHelloRequest) ProtoMessage() {}
func (x *SayHelloRequest) ProtoReflect() protoreflect.Message {
mi := &file_testing_interceptor_v1_greeter_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SayHelloRequest.ProtoReflect.Descriptor instead.
func (*SayHelloRequest) Descriptor() ([]byte, []int) {
return file_testing_interceptor_v1_greeter_proto_rawDescGZIP(), []int{0}
}
func (x *SayHelloRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
// The response message containing the greetings
type SayHelloResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *SayHelloResponse) Reset() {
*x = SayHelloResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_testing_interceptor_v1_greeter_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SayHelloResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SayHelloResponse) ProtoMessage() {}
func (x *SayHelloResponse) ProtoReflect() protoreflect.Message {
mi := &file_testing_interceptor_v1_greeter_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SayHelloResponse.ProtoReflect.Descriptor instead.
func (*SayHelloResponse) Descriptor() ([]byte, []int) {
return file_testing_interceptor_v1_greeter_proto_rawDescGZIP(), []int{1}
}
func (x *SayHelloResponse) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
var File_testing_interceptor_v1_greeter_proto protoreflect.FileDescriptor
var file_testing_interceptor_v1_greeter_proto_rawDesc = []byte{
0x0a, 0x24, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63,
0x65, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x22, 0x25,
0x0a, 0x0f, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2c, 0x0a, 0x10, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c,
0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x32, 0x71, 0x0a, 0x0e, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c,
0x6f, 0x12, 0x27, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x79, 0x48, 0x65,
0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x74, 0x65, 0x73,
0x74, 0x69, 0x6e, 0x67, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72,
0x2e, 0x76, 0x31, 0x2e, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62,
0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x3b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65,
0x70, 0x74, 0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_testing_interceptor_v1_greeter_proto_rawDescOnce sync.Once
file_testing_interceptor_v1_greeter_proto_rawDescData = file_testing_interceptor_v1_greeter_proto_rawDesc
)
func file_testing_interceptor_v1_greeter_proto_rawDescGZIP() []byte {
file_testing_interceptor_v1_greeter_proto_rawDescOnce.Do(func() {
file_testing_interceptor_v1_greeter_proto_rawDescData = protoimpl.X.CompressGZIP(file_testing_interceptor_v1_greeter_proto_rawDescData)
})
return file_testing_interceptor_v1_greeter_proto_rawDescData
}
var file_testing_interceptor_v1_greeter_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_testing_interceptor_v1_greeter_proto_goTypes = []interface{}{
(*SayHelloRequest)(nil), // 0: testing.interceptor.v1.SayHelloRequest
(*SayHelloResponse)(nil), // 1: testing.interceptor.v1.SayHelloResponse
}
var file_testing_interceptor_v1_greeter_proto_depIdxs = []int32{
0, // 0: testing.interceptor.v1.GreeterService.SayHello:input_type -> testing.interceptor.v1.SayHelloRequest
1, // 1: testing.interceptor.v1.GreeterService.SayHello:output_type -> testing.interceptor.v1.SayHelloResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_testing_interceptor_v1_greeter_proto_init() }
func file_testing_interceptor_v1_greeter_proto_init() {
if File_testing_interceptor_v1_greeter_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_testing_interceptor_v1_greeter_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SayHelloRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_testing_interceptor_v1_greeter_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SayHelloResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_testing_interceptor_v1_greeter_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_testing_interceptor_v1_greeter_proto_goTypes,
DependencyIndexes: file_testing_interceptor_v1_greeter_proto_depIdxs,
MessageInfos: file_testing_interceptor_v1_greeter_proto_msgTypes,
}.Build()
File_testing_interceptor_v1_greeter_proto = out.File
file_testing_interceptor_v1_greeter_proto_rawDesc = nil
file_testing_interceptor_v1_greeter_proto_goTypes = nil
file_testing_interceptor_v1_greeter_proto_depIdxs = nil
}
@@ -0,0 +1,103 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package interceptor
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// GreeterServiceClient is the client API for GreeterService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterServiceClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *SayHelloRequest, opts ...grpc.CallOption) (*SayHelloResponse, error)
}
type greeterServiceClient struct {
cc grpc.ClientConnInterface
}
func NewGreeterServiceClient(cc grpc.ClientConnInterface) GreeterServiceClient {
return &greeterServiceClient{cc}
}
func (c *greeterServiceClient) SayHello(ctx context.Context, in *SayHelloRequest, opts ...grpc.CallOption) (*SayHelloResponse, error) {
out := new(SayHelloResponse)
err := c.cc.Invoke(ctx, "/testing.interceptor.v1.GreeterService/SayHello", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// GreeterServiceServer is the server API for GreeterService service.
// All implementations must embed UnimplementedGreeterServiceServer
// for forward compatibility
type GreeterServiceServer interface {
// Sends a greeting
SayHello(context.Context, *SayHelloRequest) (*SayHelloResponse, error)
mustEmbedUnimplementedGreeterServiceServer()
}
// UnimplementedGreeterServiceServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterServiceServer struct {
}
func (UnimplementedGreeterServiceServer) SayHello(context.Context, *SayHelloRequest) (*SayHelloResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterServiceServer) mustEmbedUnimplementedGreeterServiceServer() {}
// UnsafeGreeterServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GreeterServiceServer will
// result in compilation errors.
type UnsafeGreeterServiceServer interface {
mustEmbedUnimplementedGreeterServiceServer()
}
func RegisterGreeterServiceServer(s grpc.ServiceRegistrar, srv GreeterServiceServer) {
s.RegisterService(&GreeterService_ServiceDesc, srv)
}
func _GreeterService_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SayHelloRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GreeterServiceServer).SayHello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/testing.interceptor.v1.GreeterService/SayHello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServiceServer).SayHello(ctx, req.(*SayHelloRequest))
}
return interceptor(ctx, in, info, handler)
}
// GreeterService_ServiceDesc is the grpc.ServiceDesc for GreeterService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var GreeterService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "testing.interceptor.v1.GreeterService",
HandlerType: (*GreeterServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _GreeterService_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "testing/interceptor/v1/greeter.proto",
}
@@ -0,0 +1,8 @@
syntax = "proto3";
package controller.api.v1;
option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api;api";
// EmptyResponse represents an empty response
message EmptyResponse {}
@@ -0,0 +1,42 @@
syntax = "proto3";
package controller.auth.v1;
option go_package = "github.com/hashicorp/boundary/internal/gen/controller/auth;auth";
// RequestInfo contains request parameters necessary for checking authn/authz
message RequestInfo {
// path of the request
string path = 10;
// method (http verb) of the request
string method = 20;
// public_id for the request
string public_id = 30;
// encrypted_token from the request
string encrypted_token = 40;
// token from the request
string token = 50;
// token_format of the request's token
uint32 token_format = 60;
// scope_id_override for the request (helpful for tests)
string scope_id_override = 70;
// user_id_override for the request (helpful for tests)
string user_id_override = 80;
// disable_authz_failures for the request (helpful for tests)
bool disable_authz_failures = 90;
// disable_auth_entirely for the request (helpful for tests)
bool disable_auth_entirely = 100;
// ticket is a unique id that allows the grpc-gateway to verify that the info
// came from its companion http proxy
string ticket = 110;
}
@@ -0,0 +1,41 @@
syntax = "proto3";
package errors.v1;
option go_package = "github.com/hashicorp/boundary/internal/gen/errors;errors";
import "controller/api/v1/error.proto";
// Err is a protobuf representation of a domain error
message Err {
// code is the error's code, which can be used to get the error's
// errorCodeInfo, which contains the error's Kind and Message
uint32 code = 10;
// msg for the error
string msg = 20;
// op represents the operation raising/propagating an error and is optional.
string op = 30;
// wrapped represents an optionally wrapped error
oneof wrapped {
// err is a wrapped Err (aka a domain error)
Err err = 40;
// std_error is a wrapped std error
string std_error = 50;
// no error or err is wrapped
bool none = 60;
}
}
// ApiError is a protobuf representation of a controller ApiError
message ApiError {
// api_error is the controller ApiError
controller.api.v1.Error api_error = 10;
// status is the status code assocatiated with the api error
int32 status = 20;
}
@@ -0,0 +1,22 @@
syntax = "proto3";
package testing.interceptor.v1;
option go_package = "github.com/hashicorp/boundary/internal/gen/testing/interceptor;interceptor";
// The greeting service definition. This is just a dead simple service that can
// be used to test interceptors. (See controller.startTestGreeterService)
service GreeterService {
// Sends a greeting
rpc SayHello(SayHelloRequest) returns (SayHelloResponse) {}
}
// The request message containing the user's name.
message SayHelloRequest {
string name = 1;
}
// The response message containing the greetings
message SayHelloResponse {
string message = 1;
}
+39 -53
View File
@@ -11,7 +11,9 @@ import (
"github.com/hashicorp/boundary/api/recovery"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/errors"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/observability/event"
"github.com/hashicorp/boundary/internal/perms"
@@ -27,7 +29,7 @@ import (
"google.golang.org/protobuf/proto"
)
type TokenFormat int
type TokenFormat uint32
const (
// We weren't given one or couldn't parse it
@@ -51,22 +53,6 @@ type key int
var verifierKey key
// RequestInfo contains request parameters necessary for checking authn/authz
type RequestInfo struct {
Path string
Method string
PublicId string
EncryptedToken string
Token string
TokenFormat TokenFormat
// The following are useful for tests
scopeIdOverride string
userIdOverride string
DisableAuthzFailures bool
DisableAuthEntirely bool
}
type VerifyResults struct {
UserId string
AuthTokenId string
@@ -108,7 +94,7 @@ type verifier struct {
authTokenRepoFn common.AuthTokenRepoFactory
serversRepoFn common.ServersRepoFactory
kms *kms.Kms
requestInfo RequestInfo
requestInfo *authpb.RequestInfo
res *perms.Resource
act action.Type
ctx context.Context
@@ -124,7 +110,7 @@ func NewVerifierContext(ctx context.Context,
authTokenRepoFn common.AuthTokenRepoFactory,
serversRepoFn common.ServersRepoFactory,
kms *kms.Kms,
requestInfo RequestInfo) context.Context {
requestInfo *authpb.RequestInfo) context.Context {
return context.WithValue(ctx, verifierKey, &verifier{
iamRepoFn: iamRepoFn,
authTokenRepoFn: authTokenRepoFn,
@@ -174,7 +160,7 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
// without fuss
if v.requestInfo.DisableAuthEntirely {
const op = "auth.(disabled).lookupScope"
ret.Scope.Id = v.requestInfo.scopeIdOverride
ret.Scope.Id = v.requestInfo.ScopeIdOverride
if ret.Scope.Id == "" {
ret.Scope.Id = opts.withScopeId
}
@@ -214,7 +200,7 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
ParentScopeId: scp.GetParentId(),
}
}
ret.UserId = v.requestInfo.userIdOverride
ret.UserId = v.requestInfo.UserIdOverride
if reqInfo != nil {
reqInfo.UserId = ret.UserId
}
@@ -291,40 +277,40 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
func (v *verifier) decryptToken(ctx context.Context) {
const op = "auth.(verifier).decryptToken"
switch v.requestInfo.TokenFormat {
case AuthTokenTypeUnknown:
case uint32(AuthTokenTypeUnknown):
// Nothing to decrypt
return
case AuthTokenTypeBearer, AuthTokenTypeSplitCookie:
case uint32(AuthTokenTypeBearer), uint32(AuthTokenTypeSplitCookie):
if v.kms == nil {
event.WriteError(ctx, op, stderrors.New("no KMS object available to authz system"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
tokenRepo, err := v.authTokenRepoFn()
if err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to get authtoken repo"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
at, err := tokenRepo.LookupAuthToken(v.ctx, v.requestInfo.PublicId)
if err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to look up auth token by public ID"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
if at == nil {
event.WriteError(ctx, op, stderrors.New("nil result from looking up auth token by public ID"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
tokenWrapper, err := v.kms.GetWrapper(v.ctx, at.GetScopeId(), kms.KeyPurposeTokens)
if err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("unable to get wrapper for tokens; continuing as anonymous user"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
@@ -333,62 +319,62 @@ func (v *verifier) decryptToken(ctx context.Context) {
case globals.ServiceTokenV1:
default:
event.WriteError(ctx, op, stderrors.New("unknown token encryption version; continuing as anonymous user"), event.WithInfo("version", version))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
marshaledToken, err := base58.FastBase58Decoding(v.requestInfo.EncryptedToken[len(globals.ServiceTokenV1):])
if err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error unmarshaling base58 token; continuing as anonymous user"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
blobInfo := new(wrapping.EncryptedBlobInfo)
if err := proto.Unmarshal(marshaledToken, blobInfo); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error decoding encrypted token; continuing as anonymous user"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
s1Bytes, err := tokenWrapper.Decrypt(v.ctx, blobInfo, []byte(v.requestInfo.PublicId))
if err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error decrypting encrypted token; continuing as anonymous user"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
var s1Info tokens.S1TokenInfo
if err := proto.Unmarshal(s1Bytes, &s1Info); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error unmarshaling token info; continuing as anonymous user"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
if v.requestInfo.TokenFormat == AuthTokenTypeUnknown || s1Info.Token == "" || v.requestInfo.PublicId == "" {
if v.requestInfo.TokenFormat == uint32(AuthTokenTypeUnknown) || s1Info.Token == "" || v.requestInfo.PublicId == "" {
event.WriteError(ctx, op, stderrors.New("after parsing, could not find valid token; continuing as anonymous user"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
v.requestInfo.Token = s1Info.Token
return
case AuthTokenTypeRecoveryKms:
case uint32(AuthTokenTypeRecoveryKms):
if v.kms == nil {
event.WriteError(ctx, op, stderrors.New("no KMS object available to authz system"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
wrapper := v.kms.GetExternalWrappers().Recovery()
if wrapper == nil {
event.WriteError(ctx, op, stderrors.New("no recovery KMS is available"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
info, err := recovery.ParseRecoveryToken(v.ctx, wrapper, v.requestInfo.EncryptedToken)
if err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("decrypt recovery token: error parsing and validating recovery token"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
// If we add the validity period to the creation time (which we've
@@ -396,18 +382,18 @@ func (v *verifier) decryptToken(ctx context.Context) {
// it's before now, it's expired and might be a replay.
if info.CreationTime.Add(globals.RecoveryTokenValidityPeriod).Before(time.Now()) {
event.WriteError(ctx, op, stderrors.New("recovery token has expired (possible replay attack)"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
repo, err := v.serversRepoFn()
if err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("decrypt recovery token: error fetching servers repo"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
if err := repo.AddRecoveryNonce(v.ctx, info.Nonce); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("decrypt recovery token: error adding nonce to database (possible replay attack)"))
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
return
}
event.WriteError(ctx, op, stderrors.New("recovery KMS was used to authorize a call"), event.WithInfo("url", v.requestInfo.Path, "method", v.requestInfo.Method))
@@ -426,15 +412,15 @@ func (v verifier) performAuthCheck(ctx context.Context) (aclResults perms.ACLRes
// Validate the token and fetch the corresponding user ID
switch v.requestInfo.TokenFormat {
case AuthTokenTypeUnknown:
case uint32(AuthTokenTypeUnknown):
// Nothing; remain as the anonymous user
case AuthTokenTypeRecoveryKms:
case uint32(AuthTokenTypeRecoveryKms):
// We validated the encrypted token in decryptToken and handled the
// nonces there, so just set the user
userId = "u_recovery"
case AuthTokenTypeBearer, AuthTokenTypeSplitCookie:
case uint32(AuthTokenTypeBearer), uint32(AuthTokenTypeSplitCookie):
if v.requestInfo.Token == "" {
// This will end up staying as the anonymous user
break
@@ -500,7 +486,7 @@ func (v verifier) performAuthCheck(ctx context.Context) (aclResults perms.ACLRes
}
// At this point we don't need to look up grants since it's automatically allowed
if v.requestInfo.TokenFormat == AuthTokenTypeRecoveryKms {
if v.requestInfo.TokenFormat == uint32(AuthTokenTypeRecoveryKms) {
aclResults.AuthenticationFinished = true
aclResults.Authorized = true
retErr = nil
@@ -561,7 +547,7 @@ func (r *VerifyResults) FetchActionSetForType(ctx context.Context, typ resource.
func (r *VerifyResults) fetchActions(id string, typ resource.Type, availableActions action.ActionSet, opt ...Option) action.ActionSet {
switch {
case r.v.requestInfo.DisableAuthEntirely,
r.v.requestInfo.TokenFormat == AuthTokenTypeRecoveryKms:
r.v.requestInfo.TokenFormat == uint32(AuthTokenTypeRecoveryKms):
// TODO: See if we can be better about what we return with the anonymous
// user and recovery KMS, perhaps given exclusionary options for each
return availableActions
@@ -598,7 +584,7 @@ func (r *VerifyResults) fetchActions(id string, typ resource.Type, availableActi
func (r *VerifyResults) FetchOutputFields(res perms.Resource, act action.Type) perms.OutputFieldsMap {
switch {
case r.v.requestInfo.TokenFormat == AuthTokenTypeRecoveryKms:
case r.v.requestInfo.TokenFormat == uint32(AuthTokenTypeRecoveryKms):
return perms.OutputFieldsMap{"*": true}
case r.v.requestInfo.DisableAuthEntirely:
return nil
@@ -611,7 +597,7 @@ func (r *VerifyResults) FetchOutputFields(res perms.Resource, act action.Type) p
// split cookies and parses it. If it cannot be parsed successfully, the issue
// is logged and we return blank, so logic will continue as the anonymous user.
// The public ID and _encrypted_ token are returned along with the token format.
func GetTokenFromRequest(ctx context.Context, kmsCache *kms.Kms, req *http.Request) (string, string, TokenFormat) {
func GetTokenFromRequest(ctx context.Context, kmsCache *kms.Kms, req *http.Request) (string, string, uint32) {
const op = "auth.GetTokenFromRequest"
// First, get the token, either from the authorization header or from split
// cookies
@@ -643,21 +629,21 @@ func GetTokenFromRequest(ctx context.Context, kmsCache *kms.Kms, req *http.Reque
// We didn't find auth info or a client screwed up and put in a blank
// header instead of nothing at all, so return blank which will indicate
// the anonymouse user
return "", "", AuthTokenTypeUnknown
return "", "", uint32(AuthTokenTypeUnknown)
}
if strings.HasPrefix(fullToken, "r_") {
return "", fullToken, AuthTokenTypeRecoveryKms
return "", fullToken, uint32(AuthTokenTypeRecoveryKms)
}
splitFullToken := strings.Split(fullToken, "_")
if len(splitFullToken) != 3 {
event.WriteError(ctx, op, stderrors.New("unexpected number of segments in token; continuing as anonymous user"), event.WithInfo("expected", 3, "found", len(splitFullToken)))
return "", "", AuthTokenTypeUnknown
return "", "", uint32(AuthTokenTypeUnknown)
}
publicId := strings.Join(splitFullToken[0:2], "_")
encryptedToken := splitFullToken[2]
return publicId, encryptedToken, receivedTokenType
return publicId, encryptedToken, uint32(receivedTokenType)
}
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/db"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/servers"
@@ -107,17 +108,17 @@ func TestAuthTokenAuthenticator(t *testing.T) {
}
// Add values for authn/authz checking
requestInfo := RequestInfo{
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
}
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = GetTokenFromRequest(context.TODO(), kms, req)
assert.Equal(t, tc.tokenFormat, requestInfo.TokenFormat)
assert.Equal(t, uint32(tc.tokenFormat), requestInfo.TokenFormat)
if tc.userId == "" {
return
}
ctx := NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo)
ctx := NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
v, ok := ctx.Value(verifierKey).(*verifier)
require.True(t, ok)
+9 -8
View File
@@ -3,6 +3,7 @@ package auth
import (
"context"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/common"
)
@@ -11,16 +12,16 @@ import (
// auth checking entirely disabled. Supported options: WithScopeId an WithUserId
// are used directly; WithKms is passed through into the verifier context.
func DisabledAuthTestContext(iamRepoFn common.IamRepoFactory, scopeId string, opt ...Option) context.Context {
reqInfo := RequestInfo{DisableAuthEntirely: true}
reqInfo := authpb.RequestInfo{DisableAuthEntirely: true}
opts := getOpts(opt...)
reqInfo.scopeIdOverride = opts.withScopeId
if reqInfo.scopeIdOverride == "" {
reqInfo.scopeIdOverride = scopeId
reqInfo.ScopeIdOverride = opts.withScopeId
if reqInfo.ScopeIdOverride == "" {
reqInfo.ScopeIdOverride = scopeId
}
reqInfo.userIdOverride = opts.withUserId
if reqInfo.userIdOverride == "" {
reqInfo.userIdOverride = "u_auth"
reqInfo.UserIdOverride = opts.withUserId
if reqInfo.UserIdOverride == "" {
reqInfo.UserIdOverride = "u_auth"
}
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
return NewVerifierContext(requestContext, iamRepoFn, nil, nil, opts.withKms, reqInfo)
return NewVerifierContext(requestContext, iamRepoFn, nil, nil, opts.withKms, &reqInfo)
}
@@ -7,6 +7,7 @@ import (
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/db"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/servers"
@@ -229,9 +230,9 @@ func TestListingScopeIds(t *testing.T) {
authTokenRepoFn,
serversRepoFn,
kms,
auth.RequestInfo{
&authpb.RequestInfo{
Token: at.GetToken(),
TokenFormat: auth.AuthTokenTypeBearer,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: at.GetPublicId(),
})
+10 -1
View File
@@ -6,6 +6,8 @@ import (
"fmt"
"sync"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/authtoken"
@@ -26,6 +28,7 @@ import (
"github.com/hashicorp/go-secure-stdlib/mlock"
"github.com/patrickmn/go-cache"
ua "go.uber.org/atomic"
"google.golang.org/grpc"
)
type Controller struct {
@@ -44,6 +47,12 @@ type Controller struct {
// Used for testing and tracking worker health
workerStatusUpdateTimes *sync.Map
// grpc gateway server
gatewayServer *grpc.Server
gatewayTicket string
gatewayListener gatewayListener
gatewayMux *runtime.ServeMux
// Repo factory methods
AuthTokenRepoFn common.AuthTokenRepoFactory
VaultCredentialRepoFn common.VaultCredentialRepoFactory
@@ -172,7 +181,7 @@ func (c *Controller) Start() error {
if err := c.scheduler.Start(c.baseContext, &c.schedulerWg); err != nil {
return fmt.Errorf("error starting scheduler: %w", err)
}
if err := c.startListeners(); err != nil {
if err := c.startListeners(c.baseContext); err != nil {
return fmt.Errorf("error starting controller listeners: %w", err)
}
+67
View File
@@ -0,0 +1,67 @@
package controller
import (
"context"
"math"
"net"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
)
// newGatewayListener will create an in-memory listener
func newGatewayListener() (gatewayListener, string) {
buffer := globals.DefaultMaxRequestSize // seems like a reasonable size for the ring buffer, but then happily change the size if more info becomes available
return bufconn.Listen(int(buffer)), ""
}
const gatewayTarget = ""
type gatewayListener interface {
net.Listener
Dial() (net.Conn, error)
}
func gatewayDialOptions(lis gatewayListener) []grpc.DialOption {
return []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
}
}
func newGatewayMux() *runtime.ServeMux {
return runtime.NewServeMux(
runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{
Marshaler: handlers.JSONMarshaler(),
}),
runtime.WithErrorHandler(handlers.ErrorHandler()),
runtime.WithForwardResponseOption(handlers.OutgoingResponseFilter),
)
}
func newGatewayServer(ctx context.Context, iamRepoFn common.IamRepoFactory, authTokenRepoFn common.AuthTokenRepoFactory, serversRepoFn common.ServersRepoFactory, kms *kms.Kms) (*grpc.Server, string, error) {
const op = "controller.newGatewayServer"
ticket, err := db.NewPrivateId("gwticket")
if err != nil {
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg("unable to generate gateway ticket"))
}
requestCtxInterceptor, err := requestCtxInterceptor(ctx, iamRepoFn, authTokenRepoFn, serversRepoFn, kms, ticket)
if err != nil {
return nil, "", err
}
return grpc.NewServer(
grpc.MaxRecvMsgSize(math.MaxInt32),
grpc.MaxSendMsgSize(math.MaxInt32),
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(requestCtxInterceptor, errorInterceptor(ctx), statusCodeInterceptor(ctx))),
), ticket, nil
}
+171 -130
View File
@@ -13,35 +13,36 @@ import (
"strings"
"time"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/gen/controller/api/services"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/observability/event"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/common"
"github.com/hashicorp/boundary/internal/servers/controller/auth"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/accounts"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/authmethods"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/authtokens"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/credentiallibraries"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/credentialstores"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/groups"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/host_catalogs"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/host_sets"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/hosts"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/managed_groups"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/roles"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/scopes"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/sessions"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/targets"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/users"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-secure-stdlib/listenerutil"
"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/mr-tron/base58"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/proto"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/authtokens"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/groups"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/host_catalogs"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/hosts"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/roles"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/scopes"
"github.com/hashicorp/boundary/internal/servers/controller/handlers/users"
)
type HandlerProperties struct {
@@ -78,127 +79,169 @@ func handleGrpcGateway(c *Controller, props HandlerProperties) (http.Handler, er
// Register*ServiceHandlerServer methods ignore the passed in ctx. Using it
// now however in case this changes in the future.
ctx := props.CancelCtx
mux := runtime.NewServeMux(
runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{
Marshaler: handlers.JSONMarshaler(),
}),
runtime.WithErrorHandler(handlers.ErrorHandler()),
runtime.WithForwardResponseOption(handlers.OutgoingInterceptor),
)
hcs, err := host_catalogs.NewService(c.StaticHostRepoFn, c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create host catalog handler service: %w", err)
currentServices := c.gatewayServer.GetServiceInfo()
dialOptions := gatewayDialOptions(c.gatewayListener)
if _, ok := currentServices[services.HostCatalogService_ServiceDesc.ServiceName]; !ok {
hcs, err := host_catalogs.NewService(c.StaticHostRepoFn, c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create host catalog handler service: %w", err)
}
services.RegisterHostCatalogServiceServer(c.gatewayServer, hcs)
if err := services.RegisterHostCatalogServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register host catalog service handler: %w", err)
}
}
if err := services.RegisterHostCatalogServiceHandlerServer(ctx, mux, hcs); err != nil {
return nil, fmt.Errorf("failed to register host catalog service handler: %w", err)
if _, ok := currentServices[services.HostSetService_ServiceDesc.ServiceName]; !ok {
hss, err := host_sets.NewService(c.StaticHostRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create host set handler service: %w", err)
}
services.RegisterHostSetServiceServer(c.gatewayServer, hss)
if err := services.RegisterHostSetServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register host set service handler: %w", err)
}
}
hss, err := host_sets.NewService(c.StaticHostRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create host set handler service: %w", err)
if _, ok := currentServices[services.HostService_ServiceDesc.ServiceName]; !ok {
hs, err := hosts.NewService(c.StaticHostRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create host handler service: %w", err)
}
services.RegisterHostServiceServer(c.gatewayServer, hs)
if err := services.RegisterHostServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register host service handler: %w", err)
}
}
if err := services.RegisterHostSetServiceHandlerServer(ctx, mux, hss); err != nil {
return nil, fmt.Errorf("failed to register host set service handler: %w", err)
if _, ok := currentServices[services.AccountService_ServiceDesc.ServiceName]; !ok {
accts, err := accounts.NewService(c.PasswordAuthRepoFn, c.OidcRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create account handler service: %w", err)
}
services.RegisterAccountServiceServer(c.gatewayServer, accts)
if err := services.RegisterAccountServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register account service handler: %w", err)
}
}
hs, err := hosts.NewService(c.StaticHostRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create host handler service: %w", err)
if _, ok := currentServices[services.AuthMethodService_ServiceDesc.ServiceName]; !ok {
authMethods, err := authmethods.NewService(c.kms, c.PasswordAuthRepoFn, c.OidcRepoFn, c.IamRepoFn, c.AuthTokenRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create auth method handler service: %w", err)
}
services.RegisterAuthMethodServiceServer(c.gatewayServer, authMethods)
if err := services.RegisterAuthMethodServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register auth method service handler: %w", err)
}
}
if err := services.RegisterHostServiceHandlerServer(ctx, mux, hs); err != nil {
return nil, fmt.Errorf("failed to register host service handler: %w", err)
if _, ok := currentServices[services.AuthTokenService_ServiceDesc.ServiceName]; !ok {
authtoks, err := authtokens.NewService(c.AuthTokenRepoFn, c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create auth token handler service: %w", err)
}
services.RegisterAuthTokenServiceServer(c.gatewayServer, authtoks)
if err := services.RegisterAuthTokenServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register auth token service handler: %w", err)
}
}
accts, err := accounts.NewService(c.PasswordAuthRepoFn, c.OidcRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create account handler service: %w", err)
if _, ok := currentServices[services.ScopeService_ServiceDesc.ServiceName]; !ok {
os, err := scopes.NewService(c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create scope handler service: %w", err)
}
services.RegisterScopeServiceServer(c.gatewayServer, os)
if err := services.RegisterScopeServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register scope service handler: %w", err)
}
}
if err := services.RegisterAccountServiceHandlerServer(ctx, mux, accts); err != nil {
return nil, fmt.Errorf("failed to register account service handler: %w", err)
if _, ok := currentServices[services.UserService_ServiceDesc.ServiceName]; !ok {
us, err := users.NewService(c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create user handler service: %w", err)
}
services.RegisterUserServiceServer(c.gatewayServer, us)
if err := services.RegisterUserServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register user service handler: %w", err)
}
}
authMethods, err := authmethods.NewService(c.kms, c.PasswordAuthRepoFn, c.OidcRepoFn, c.IamRepoFn, c.AuthTokenRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create auth method handler service: %w", err)
if _, ok := currentServices[services.TargetService_ServiceDesc.ServiceName]; !ok {
ts, err := targets.NewService(
c.kms,
c.TargetRepoFn,
c.IamRepoFn,
c.ServersRepoFn,
c.SessionRepoFn,
c.StaticHostRepoFn,
c.VaultCredentialRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create target handler service: %w", err)
}
services.RegisterTargetServiceServer(c.gatewayServer, ts)
if err := services.RegisterTargetServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register target service handler: %w", err)
}
}
if err := services.RegisterAuthMethodServiceHandlerServer(ctx, mux, authMethods); err != nil {
return nil, fmt.Errorf("failed to register auth method service handler: %w", err)
if _, ok := currentServices[services.GroupService_ServiceDesc.ServiceName]; !ok {
gs, err := groups.NewService(c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create group handler service: %w", err)
}
services.RegisterGroupServiceServer(c.gatewayServer, gs)
if err := services.RegisterGroupServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register group service handler: %w", err)
}
}
authtoks, err := authtokens.NewService(c.AuthTokenRepoFn, c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create auth token handler service: %w", err)
if _, ok := currentServices[services.RoleService_ServiceDesc.ServiceName]; !ok {
rs, err := roles.NewService(c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create role handler service: %w", err)
}
services.RegisterRoleServiceServer(c.gatewayServer, rs)
if err := services.RegisterRoleServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register role service handler: %w", err)
}
}
if err := services.RegisterAuthTokenServiceHandlerServer(ctx, mux, authtoks); err != nil {
return nil, fmt.Errorf("failed to register auth token service handler: %w", err)
if _, ok := currentServices[services.SessionService_ServiceDesc.ServiceName]; !ok {
ss, err := sessions.NewService(c.SessionRepoFn, c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create session handler service: %w", err)
}
services.RegisterSessionServiceServer(c.gatewayServer, ss)
if err := services.RegisterSessionServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register session service handler: %w", err)
}
}
os, err := scopes.NewService(c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create scope handler service: %w", err)
if _, ok := currentServices[services.ManagedGroupService_ServiceDesc.ServiceName]; !ok {
mgs, err := managed_groups.NewService(c.OidcRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create managed groups handler service: %w", err)
}
services.RegisterManagedGroupServiceServer(c.gatewayServer, mgs)
if err := services.RegisterManagedGroupServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register managed groups service handler: %w", err)
}
}
if err := services.RegisterScopeServiceHandlerServer(ctx, mux, os); err != nil {
return nil, fmt.Errorf("failed to register scope service handler: %w", err)
if _, ok := currentServices[services.CredentialStoreService_ServiceDesc.ServiceName]; !ok {
cs, err := credentialstores.NewService(c.VaultCredentialRepoFn, c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create credential store handler service: %w", err)
}
services.RegisterCredentialStoreServiceServer(c.gatewayServer, cs)
if err := services.RegisterCredentialStoreServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register credential store service handler: %w", err)
}
}
us, err := users.NewService(c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create user handler service: %w", err)
}
if err := services.RegisterUserServiceHandlerServer(ctx, mux, us); err != nil {
return nil, fmt.Errorf("failed to register user service handler: %w", err)
}
ts, err := targets.NewService(
c.kms,
c.TargetRepoFn,
c.IamRepoFn,
c.ServersRepoFn,
c.SessionRepoFn,
c.StaticHostRepoFn,
c.VaultCredentialRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create target handler service: %w", err)
}
if err := services.RegisterTargetServiceHandlerServer(ctx, mux, ts); err != nil {
return nil, fmt.Errorf("failed to register target service handler: %w", err)
}
gs, err := groups.NewService(c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create group handler service: %w", err)
}
if err := services.RegisterGroupServiceHandlerServer(ctx, mux, gs); err != nil {
return nil, fmt.Errorf("failed to register group service handler: %w", err)
}
rs, err := roles.NewService(c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create role handler service: %w", err)
}
if err := services.RegisterRoleServiceHandlerServer(ctx, mux, rs); err != nil {
return nil, fmt.Errorf("failed to register role service handler: %w", err)
}
ss, err := sessions.NewService(c.SessionRepoFn, c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create session handler service: %w", err)
}
if err := services.RegisterSessionServiceHandlerServer(ctx, mux, ss); err != nil {
return nil, fmt.Errorf("failed to register session service handler: %w", err)
}
mgs, err := managed_groups.NewService(c.OidcRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create managed groups handler service: %w", err)
}
if err := services.RegisterManagedGroupServiceHandlerServer(ctx, mux, mgs); err != nil {
return nil, fmt.Errorf("failed to register managed groups service handler: %w", err)
}
cs, err := credentialstores.NewService(c.VaultCredentialRepoFn, c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create credential store handler service: %w", err)
}
if err := services.RegisterCredentialStoreServiceHandlerServer(ctx, mux, cs); err != nil {
return nil, fmt.Errorf("failed to register credential store service handler: %w", err)
}
cl, err := credentiallibraries.NewService(c.VaultCredentialRepoFn, c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create credential library handler service: %w", err)
}
if err := services.RegisterCredentialLibraryServiceHandlerServer(ctx, mux, cl); err != nil {
return nil, fmt.Errorf("failed to register credential library service handler: %w", err)
if _, ok := currentServices[services.CredentialLibraryService_ServiceDesc.ServiceName]; !ok {
cl, err := credentiallibraries.NewService(c.VaultCredentialRepoFn, c.IamRepoFn)
if err != nil {
return nil, fmt.Errorf("failed to create credential library handler service: %w", err)
}
services.RegisterCredentialLibraryServiceServer(c.gatewayServer, cl)
if err := services.RegisterCredentialLibraryServiceHandlerFromEndpoint(ctx, c.gatewayMux, gatewayTarget, dialOptions); err != nil {
return nil, fmt.Errorf("failed to register credential library service handler: %w", err)
}
}
return mux, nil
return c.gatewayMux, nil
}
func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProperties) http.Handler {
@@ -236,29 +279,27 @@ func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProp
}
// Add values for authn/authz checking
requestInfo := auth.RequestInfo{
requestInfo := authpb.RequestInfo{
Path: r.URL.Path,
Method: r.Method,
DisableAuthzFailures: disableAuthzFailures,
}
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = auth.GetTokenFromRequest(ctx, c.kms, r)
ctx = auth.NewVerifierContext(ctx, c.IamRepoFn, c.AuthTokenRepoFn, c.ServersRepoFn, c.kms, requestInfo)
// Add general request information to the context. The information from
// the auth verifier context is pretty specifically curated to
// authentication/authorization verification so this is more
// general-purpose.
//
// We could use requests.NewRequestContext but this saves an immediate
// lookup.
ctx = context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{
Path: r.URL.Path,
Method: r.Method,
})
// Set the context back on the request
r = r.WithContext(ctx)
// Serialize the request info to send it across the wire to the
// grpc-gateway via an http header
requestInfo.Ticket = c.gatewayTicket // allows the grpc-gateway to verify the request info came from it's in-memory companion http proxy
marshalledRequestInfo, err := proto.Marshal(&requestInfo)
if err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error marshaling request info"))
w.WriteHeader(http.StatusInternalServerError)
return
}
// Use the default grpc-gateway mapping rule to pass the request info as
// metadata.
// See: https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime#DefaultHeaderMatcher
r.Header.Set("Grpc-Metadata-"+requestInfoMdKey, base58.FastBase58Encoding(marshalledRequestInfo))
h.ServeHTTP(w, r)
})
@@ -389,7 +389,7 @@ func (s Service) DeleteAuthMethod(ctx context.Context, req *pbs.DeleteAuthMethod
if err != nil {
return nil, err
}
return nil, nil
return &pbs.DeleteAuthMethodResponse{}, nil
}
// Authenticate implements the interface pbs.AuthenticationServiceServer.
@@ -428,12 +428,14 @@ func TestDelete(t *testing.T) {
req: &pbs.DeleteAuthMethodRequest{
Id: pwam.GetPublicId(),
},
res: &pbs.DeleteAuthMethodResponse{},
},
{
name: "Delete an Existing OIDC AuthMethod",
req: &pbs.DeleteAuthMethodRequest{
Id: oidcam.GetPublicId(),
},
res: &pbs.DeleteAuthMethodResponse{},
},
{
name: "Delete bad auth_method id",
@@ -13,6 +13,8 @@ import (
"time"
"github.com/google/go-cmp/cmp"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/authtoken"
@@ -206,9 +208,9 @@ func TestList_FilterNonPublic(t *testing.T) {
atRepoFn,
serversRepoFn,
kmsCache,
auth.RequestInfo{
&authpb.RequestInfo{
Token: at.GetToken(),
TokenFormat: auth.AuthTokenTypeBearer,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: at.GetPublicId(),
})
}(),
@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/db"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/requests"
@@ -87,15 +88,15 @@ func TestGetSelf(t *testing.T) {
require, assert := require.New(t), assert.New(t)
// Setup the auth request information
req := httptest.NewRequest("GET", fmt.Sprintf("http://127.0.0.1/v1/auth-tokens/%s", tc.readId), nil)
requestInfo := auth.RequestInfo{
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
TokenFormat: auth.AuthTokenTypeBearer,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: tc.token.GetPublicId(),
Token: tc.token.GetToken(),
}
ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo)
ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
ctx = context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{})
got, err := a.GetAuthToken(ctx, &pbs.GetAuthTokenRequest{Id: tc.readId})
if tc.err != nil {
@@ -236,15 +237,15 @@ func TestList_Self(t *testing.T) {
require, assert := require.New(t), assert.New(t)
// Setup the auth request information
req := httptest.NewRequest("GET", fmt.Sprintf("http://127.0.0.1/v1/auth-tokens?scope_id=%s", o.GetPublicId()), nil)
requestInfo := auth.RequestInfo{
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
TokenFormat: auth.AuthTokenTypeBearer,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: tc.requester.GetPublicId(),
Token: tc.requester.GetToken(),
}
ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo)
ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
got, err := a.ListAuthTokens(ctx, &pbs.ListAuthTokensRequest{ScopeId: o.GetPublicId()})
require.NoError(err)
require.Len(got.Items, 1)
@@ -482,15 +483,15 @@ func TestDeleteSelf(t *testing.T) {
require := require.New(t)
// Setup the auth request information
req := httptest.NewRequest("DELETE", fmt.Sprintf("http://127.0.0.1/v1/auth-tokens/%s", tc.deleteId), nil)
requestInfo := auth.RequestInfo{
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
TokenFormat: auth.AuthTokenTypeBearer,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: tc.token.GetPublicId(),
Token: tc.token.GetToken(),
}
ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo)
ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
got, err := a.DeleteAuthToken(ctx, &pbs.DeleteAuthTokenRequest{Id: tc.deleteId})
if tc.err != nil {
require.EqualError(err, tc.err.Error())
@@ -555,6 +555,7 @@ func TestDelete(t *testing.T) {
name string
id string
err error
res *pbs.DeleteCredentialLibraryResponse
}{
{
name: "success",
@@ -574,7 +575,7 @@ func TestDelete(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, gErr := s.DeleteCredentialLibrary(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.DeleteCredentialLibraryRequest{Id: tc.id})
assert.Nil(t, got)
assert.EqualValuesf(t, tc.res, got, "DeleteCredentialLibrary got response %q, wanted %q", got, tc.res)
if tc.err != nil {
require.Error(t, gErr)
assert.True(t, errors.Is(gErr, tc.err))
@@ -674,6 +674,7 @@ func TestDelete(t *testing.T) {
name string
id string
err error
res *pbs.DeleteCredentialStoreResponse
}{
{
name: "success",
@@ -693,7 +694,7 @@ func TestDelete(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, gErr := s.DeleteCredentialStore(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.DeleteCredentialStoreRequest{Id: tc.id})
assert.Nil(t, got)
assert.EqualValuesf(t, tc.res, got, "DeleteCredentialStore got response %q, wanted %q", got, tc.res)
if tc.err != nil {
require.Error(t, gErr)
assert.True(t, errors.Is(gErr, tc.err))
+143 -51
View File
@@ -5,31 +5,46 @@ import (
"fmt"
"io"
"net/http"
"regexp"
"sort"
"strconv"
"strings"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/hashicorp/boundary/internal/errors"
pb "github.com/hashicorp/boundary/internal/gen/controller/api"
pberrors "github.com/hashicorp/boundary/internal/gen/errors"
"github.com/hashicorp/boundary/internal/observability/event"
"github.com/mr-tron/base58"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
const (
genericUniquenessMsg = "Invalid request. Request attempted to make second resource with the same field value that must be unique."
genericNotFoundMsg = "Unable to find requested resource."
// domainErrHeader defines an http header for encoded domain errors from the
// grpc server.
domainErrHeader = "x-domain-err"
// domainErrMetadataHeader defines an http header for encoded domain errors from the
// grpc server via metadata
domainErrMetadataHeader = "Grpc-Metadata-X-Domain-Err"
apiErrHeader = "x-api-err"
apiErrMetadataHeader = "Grpc-Metadata-X-Api-Err"
)
type apiError struct {
status int32
inner *pb.Error
type ApiError struct {
Status int32
Inner *pb.Error
}
func (e *apiError) Error() string {
res := fmt.Sprintf("Status: %d, Kind: %q, Error: %q", e.status, e.inner.GetKind(), e.inner.GetMessage())
func (e *ApiError) Error() string {
res := fmt.Sprintf("Status: %d, Kind: %q, Error: %q", e.Status, e.Inner.GetKind(), e.Inner.GetMessage())
var dets []string
for _, rf := range e.inner.GetDetails().GetRequestFields() {
for _, rf := range e.Inner.GetDetails().GetRequestFields() {
dets = append(dets, fmt.Sprintf("{name: %q, desc: %q}", rf.GetName(), rf.GetDescription()))
}
if len(dets) > 0 {
@@ -39,19 +54,19 @@ func (e *apiError) Error() string {
return res
}
func (e *apiError) Is(target error) bool {
var tApiErr *apiError
func (e *ApiError) Is(target error) bool {
var tApiErr *ApiError
if !errors.As(target, &tApiErr) {
return false
}
return tApiErr.inner.Kind == e.inner.Kind && tApiErr.status == e.status
return tApiErr.Inner.Kind == e.Inner.Kind && tApiErr.Status == e.Status
}
// ApiErrorWithCode returns an api error with the provided code.
func ApiErrorWithCode(c codes.Code) error {
return &apiError{
status: int32(runtime.HTTPStatusFromCode(c)),
inner: &pb.Error{
return &ApiError{
Status: int32(runtime.HTTPStatusFromCode(c)),
Inner: &pb.Error{
Kind: c.String(),
},
}
@@ -59,9 +74,9 @@ func ApiErrorWithCode(c codes.Code) error {
// ApiErrorWithCodeAndMessage returns an api error with the provided code and message.
func ApiErrorWithCodeAndMessage(c codes.Code, msg string, args ...interface{}) error {
return &apiError{
status: int32(runtime.HTTPStatusFromCode(c)),
inner: &pb.Error{
return &ApiError{
Status: int32(runtime.HTTPStatusFromCode(c)),
Inner: &pb.Error{
Kind: c.String(),
Message: fmt.Sprintf(msg, args...),
},
@@ -70,9 +85,9 @@ func ApiErrorWithCodeAndMessage(c codes.Code, msg string, args ...interface{}) e
// NotFoundError returns an ApiError indicating a resource couldn't be found.
func NotFoundError() error {
return &apiError{
status: http.StatusNotFound,
inner: &pb.Error{
return &ApiError{
Status: http.StatusNotFound,
Inner: &pb.Error{
Kind: codes.NotFound.String(),
Message: "Resource not found.",
},
@@ -80,19 +95,19 @@ func NotFoundError() error {
}
// NotFoundErrorf returns an ApiError indicating a resource couldn't be found.
func NotFoundErrorf(msg string, a ...interface{}) *apiError {
return &apiError{
status: http.StatusNotFound,
inner: &pb.Error{
func NotFoundErrorf(msg string, a ...interface{}) *ApiError {
return &ApiError{
Status: http.StatusNotFound,
Inner: &pb.Error{
Kind: codes.NotFound.String(),
Message: fmt.Sprintf(msg, a...),
},
}
}
var unauthorizedError = &apiError{
status: http.StatusForbidden,
inner: &pb.Error{
var unauthorizedError = &ApiError{
Status: http.StatusForbidden,
Inner: &pb.Error{
Kind: codes.PermissionDenied.String(),
Message: "Forbidden.",
},
@@ -102,9 +117,9 @@ func ForbiddenError() error {
return unauthorizedError
}
var unauthenticatedError = &apiError{
status: http.StatusUnauthorized,
inner: &pb.Error{
var unauthenticatedError = &ApiError{
Status: http.StatusUnauthorized,
Inner: &pb.Error{
Kind: codes.Unauthenticated.String(),
Message: "Unauthenticated, or invalid token.",
},
@@ -114,47 +129,49 @@ func UnauthenticatedError() error {
return unauthenticatedError
}
func InvalidArgumentErrorf(msg string, fields map[string]string) *apiError {
func InvalidArgumentErrorf(msg string, fields map[string]string) *ApiError {
const op = "handlers.InvalidArgumentErrorf"
ctx := context.TODO()
err := ApiErrorWithCodeAndMessage(codes.InvalidArgument, msg)
var apiErr *apiError
var apiErr *ApiError
if !errors.As(err, &apiErr) {
event.WriteError(ctx, op, err, event.WithInfoMsg("Unable to build invalid argument api error."))
}
if len(fields) > 0 {
apiErr.inner.Details = &pb.ErrorDetails{}
apiErr.Inner.Details = &pb.ErrorDetails{}
}
for k, v := range fields {
apiErr.inner.Details.RequestFields = append(apiErr.inner.Details.RequestFields, &pb.FieldError{Name: k, Description: v})
apiErr.Inner.Details.RequestFields = append(apiErr.Inner.Details.RequestFields, &pb.FieldError{Name: k, Description: v})
}
sort.Slice(apiErr.inner.GetDetails().GetRequestFields(), func(i, j int) bool {
return apiErr.inner.Details.RequestFields[i].GetName() < apiErr.inner.Details.RequestFields[j].GetName()
sort.Slice(apiErr.Inner.GetDetails().GetRequestFields(), func(i, j int) bool {
return apiErr.Inner.Details.RequestFields[i].GetName() < apiErr.Inner.Details.RequestFields[j].GetName()
})
return apiErr
}
var statusRegEx = regexp.MustCompile("Status: ([0-9]+), Kind: \"(.*)\", Error: \"(.*)\"")
// Converts a known errors into an error that can presented to an end user over the API.
func backendErrorToApiError(inErr error) *apiError {
func backendErrorToApiError(inErr error) *ApiError {
stErr := status.Convert(inErr)
switch {
case errors.Is(inErr, runtime.ErrNotMatch):
// grpc gateway uses this error when the path was not matched, but the error uses codes.Unimplemented which doesn't match the intention.
// Overwrite the error to match our expected behavior.
return &apiError{
status: http.StatusNotFound,
inner: &pb.Error{
return &ApiError{
Status: http.StatusNotFound,
Inner: &pb.Error{
Kind: codes.NotFound.String(),
Message: http.StatusText(http.StatusNotFound),
},
}
case status.Code(inErr) == codes.Unimplemented:
// Instead of returning a 501 we always want to return a 405 when a method isn't implemented.
return &apiError{
status: http.StatusMethodNotAllowed,
inner: &pb.Error{
return &ApiError{
Status: http.StatusMethodNotAllowed,
Inner: &pb.Error{
Kind: codes.Unimplemented.String(),
Message: stErr.Message(),
},
@@ -176,11 +193,23 @@ func backendErrorToApiError(inErr error) *apiError {
statusCode = int32(domainErr.Code)
}
// perhaps the error is from the grpc gateway, so match against the known
// apiError msg format.
if found := statusRegEx.FindStringSubmatch(stErr.Message()); len(found) == 4 {
u32, err := strconv.ParseInt(found[1], 10, 32)
if err == nil { // notice it's testing for NO err
return &ApiError{
Status: int32(u32),
Inner: &pb.Error{Kind: found[2], Message: found[3]},
}
}
}
// TODO: Don't return potentially sensitive information (like which user id an account
// is already associated with when attempting to re-associate it).
return &apiError{
status: statusCode,
inner: &pb.Error{Kind: codes.Internal.String(), Message: inErr.Error()},
return &ApiError{
Status: statusCode,
Inner: &pb.Error{Kind: codes.Internal.String(), Message: inErr.Error()},
}
}
@@ -189,7 +218,70 @@ func ErrorHandler() runtime.ErrorHandlerFunc {
const errorFallback = `{"error": "failed to marshal error message"}`
return func(ctx context.Context, _ *runtime.ServeMux, mar runtime.Marshaler, w http.ResponseWriter, r *http.Request, inErr error) {
// API specified error, otherwise we need to translate repo/db errors.
var apiErr *apiError
// the grpc server will encoded domain errors into the x-domain-err, so
// let's check there first. (see: controller.errorInterceptor)
md, ok := runtime.ServerMetadataFromContext(ctx)
if ok {
defer func() {
// make sure we don't leak the headers that were used as a comm
// channel between the grpc server and the http proxy
delete(md.HeaderMD, domainErrHeader)
delete(w.Header(), domainErrMetadataHeader)
delete(md.HeaderMD, apiErrHeader)
delete(w.Header(), apiErrMetadataHeader)
}()
domainErrHdrs := md.HeaderMD.Get(domainErrHeader)
apiErrHdrs := md.HeaderMD.Get(apiErrHeader)
switch {
case len(domainErrHdrs) > 0:
decoded, err := base58.FastBase58Decoding(domainErrHdrs[0])
if err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to decode domain err header"))
w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, errorFallback); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to write response"))
}
return
}
var pbErr pberrors.Err
if err := proto.Unmarshal(decoded, &pbErr); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to marshal domain err header"))
w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, errorFallback); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to write response"))
}
return
}
inErr = errors.FromPbErrors(&pbErr)
case len(apiErrHdrs) > 0:
decoded, err := base58.FastBase58Decoding(apiErrHdrs[0])
if err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to decode api err header"))
w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, errorFallback); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to write response"))
}
return
}
var pbApiErr pberrors.ApiError
if err := proto.Unmarshal(decoded, &pbApiErr); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to marshal api err header"))
w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, errorFallback); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to write response"))
}
return
}
inErr = &ApiError{
Status: pbApiErr.Status,
Inner: pbApiErr.ApiError,
}
}
}
var apiErr *ApiError
isApiErr := errors.As(inErr, &apiErr)
if !isApiErr {
if err := backendErrorToApiError(inErr); err != nil && !errors.As(err, &apiErr) {
@@ -197,13 +289,13 @@ func ErrorHandler() runtime.ErrorHandlerFunc {
}
}
if apiErr.status == http.StatusInternalServerError {
if apiErr.Status == http.StatusInternalServerError {
event.WriteError(ctx, op, inErr, event.WithInfoMsg("internal error returned"))
}
buf, merr := mar.Marshal(apiErr.inner)
buf, merr := mar.Marshal(apiErr.Inner)
if merr != nil {
event.WriteError(ctx, op, merr, event.WithInfoMsg("failed to marshal error response", "response", fmt.Sprintf("%#v", apiErr.inner)))
event.WriteError(ctx, op, merr, event.WithInfoMsg("failed to marshal error response", "response", fmt.Sprintf("%#v", apiErr.Inner)))
w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, errorFallback); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to write response"))
@@ -211,8 +303,8 @@ func ErrorHandler() runtime.ErrorHandlerFunc {
return
}
w.Header().Set("Content-Type", mar.ContentType(apiErr.inner))
w.WriteHeader(int(apiErr.status))
w.Header().Set("Content-Type", mar.ContentType(apiErr.Inner))
w.WriteHeader(int(apiErr.Status))
if _, err := w.Write(buf); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("failed to send response chunk"))
return
@@ -221,5 +313,5 @@ func ErrorHandler() runtime.ErrorHandlerFunc {
}
func ToApiError(e error) *pb.Error {
return backendErrorToApiError(e).inner
return backendErrorToApiError(e).Inner
}
@@ -32,14 +32,14 @@ func TestApiErrorHandler(t *testing.T) {
testCases := []struct {
name string
err error
expected apiError
expected ApiError
}{
{
name: "Not Found",
err: NotFoundErrorf("Test"),
expected: apiError{
status: http.StatusNotFound,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusNotFound,
Inner: &pb.Error{
Kind: "NotFound",
Message: "Test",
},
@@ -51,9 +51,9 @@ func TestApiErrorHandler(t *testing.T) {
"k1": "v1",
"k2": "v2",
}),
expected: apiError{
status: http.StatusBadRequest,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusBadRequest,
Inner: &pb.Error{
Kind: "InvalidArgument",
Message: "Test",
Details: &pb.ErrorDetails{
@@ -74,9 +74,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "GrpcGateway Routing Error",
err: runtime.ErrNotMatch,
expected: apiError{
status: http.StatusNotFound,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusNotFound,
Inner: &pb.Error{
Kind: "NotFound",
Message: http.StatusText(http.StatusNotFound),
},
@@ -85,9 +85,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Unimplemented error",
err: status.Error(codes.Unimplemented, "Test"),
expected: apiError{
status: http.StatusMethodNotAllowed,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusMethodNotAllowed,
Inner: &pb.Error{
Kind: "Unimplemented",
Message: "Test",
},
@@ -96,9 +96,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Unknown error",
err: stderrors.New("Some random error"),
expected: apiError{
status: http.StatusInternalServerError,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusInternalServerError,
Inner: &pb.Error{
Kind: "Internal",
Message: "Some random error",
},
@@ -107,9 +107,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Domain error Db invalid public id",
err: errors.E(ctx, errors.WithCode(errors.InvalidPublicId)),
expected: apiError{
status: http.StatusInternalServerError,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusInternalServerError,
Inner: &pb.Error{
Kind: "Internal",
Message: "invalid public id, parameter violation: error #102",
},
@@ -118,9 +118,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Domain error Db invalid parameter",
err: errors.E(ctx, errors.WithCode(errors.InvalidParameter)),
expected: apiError{
status: http.StatusInternalServerError,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusInternalServerError,
Inner: &pb.Error{
Kind: "Internal",
Message: "invalid parameter, parameter violation: error #100",
},
@@ -129,9 +129,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Domain error Db invalid field mask",
err: errors.E(ctx, errors.WithCode(errors.InvalidFieldMask)),
expected: apiError{
status: http.StatusBadRequest,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusBadRequest,
Inner: &pb.Error{
Kind: "InvalidArgument",
Message: "Error in provided request",
Details: &pb.ErrorDetails{RequestFields: []*pb.FieldError{{Name: "update_mask", Description: "Invalid update mask provided."}}},
@@ -141,9 +141,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Domain error Db empty field mask",
err: errors.E(ctx, errors.WithCode(errors.EmptyFieldMask)),
expected: apiError{
status: http.StatusBadRequest,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusBadRequest,
Inner: &pb.Error{
Kind: "InvalidArgument",
Message: "Error in provided request",
Details: &pb.ErrorDetails{RequestFields: []*pb.FieldError{{Name: "update_mask", Description: "Invalid update mask provided."}}},
@@ -153,9 +153,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Domain error Db not unqiue",
err: errors.E(ctx, errors.WithCode(errors.NotUnique)),
expected: apiError{
status: http.StatusBadRequest,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusBadRequest,
Inner: &pb.Error{
Kind: "InvalidArgument",
Message: genericUniquenessMsg,
},
@@ -164,9 +164,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Domain error Db record not found",
err: errors.E(ctx, errors.WithCode(errors.RecordNotFound)),
expected: apiError{
status: http.StatusNotFound,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusNotFound,
Inner: &pb.Error{
Kind: "NotFound",
Message: genericNotFoundMsg,
},
@@ -175,9 +175,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Domain error Db multiple records",
err: errors.E(ctx, errors.WithCode(errors.MultipleRecords)),
expected: apiError{
status: http.StatusInternalServerError,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusInternalServerError,
Inner: &pb.Error{
Kind: "Internal",
Message: "multiple records, search issue: error #1101",
},
@@ -186,9 +186,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Domain error account already associated",
err: errors.E(ctx, errors.WithCode(errors.AccountAlreadyAssociated)),
expected: apiError{
status: http.StatusBadRequest,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusBadRequest,
Inner: &pb.Error{
Kind: "InvalidArgument",
Message: "account already associated with another user, parameter violation: error #114",
},
@@ -197,9 +197,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Wrapped domain error",
err: errors.E(ctx, errors.WithCode(errors.InvalidAddress), errors.WithMsg("test msg"), errors.WithWrap(errors.E(ctx, errors.WithCode(errors.NotNull), errors.WithMsg("inner msg")))),
expected: apiError{
status: http.StatusInternalServerError,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusInternalServerError,
Inner: &pb.Error{
Kind: "Internal",
Message: "test msg: parameter violation: error #101: inner msg: integrity violation: error #1001",
},
@@ -208,9 +208,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Forbidden domain error",
err: errors.E(ctx, errors.WithCode(errors.Forbidden), errors.WithMsg("test msg")),
expected: apiError{
status: http.StatusForbidden,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusForbidden,
Inner: &pb.Error{
Kind: "Internal",
Message: "test msg: unknown: error #403",
},
@@ -219,9 +219,9 @@ func TestApiErrorHandler(t *testing.T) {
{
name: "Wrapped forbidden domain error",
err: fmt.Errorf("got error: %w", errors.E(ctx, errors.WithCode(errors.Forbidden), errors.WithMsg("test msg"))),
expected: apiError{
status: http.StatusForbidden,
inner: &pb.Error{
expected: ApiError{
Status: http.StatusForbidden,
Inner: &pb.Error{
Kind: "Internal",
Message: "got error: test msg: unknown: error #403",
},
@@ -234,7 +234,7 @@ func TestApiErrorHandler(t *testing.T) {
w := httptest.NewRecorder()
tested(ctx, mux, outMarsh, w, req, tc.err)
resp := w.Result()
assert.EqualValues(tc.expected.status, resp.StatusCode)
assert.EqualValues(tc.expected.Status, resp.StatusCode)
got, err := ioutil.ReadAll(resp.Body)
require.NoError(err)
@@ -243,8 +243,8 @@ func TestApiErrorHandler(t *testing.T) {
err = inMarsh.Unmarshal(got, gotErr)
require.NoError(err)
assert.Equal(tc.expected.status, int32(resp.StatusCode))
assert.Empty(cmp.Diff(tc.expected.inner, gotErr, protocmp.Transform()))
assert.Equal(tc.expected.Status, int32(resp.StatusCode))
assert.Empty(cmp.Diff(tc.expected.Inner, gotErr, protocmp.Transform()))
})
}
}
@@ -417,6 +417,7 @@ func TestDelete_twice(t *testing.T) {
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), o.PublicId, kms.KeyPurposeDatabase)
require.NoError(err)
oidcAm := oidc.TestAuthMethod(
t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState,
@@ -1,26 +0,0 @@
package handlers
import (
"context"
"net/http"
"net/http/httptest"
"testing"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
)
func TestOutgoingSplitCookie(t *testing.T) {
rec := httptest.NewRecorder()
attrs, err := ProtoToStruct(&pb.AuthToken{Token: "t_abc_1234567890"})
require.NoError(t, err)
attrs.GetFields()["token_type"] = structpb.NewStringValue("cookie")
require.NoError(t, OutgoingInterceptor(context.Background(), rec, &pbs.AuthenticateResponse{Attributes: attrs}))
assert.ElementsMatch(t, rec.Result().Cookies(), []*http.Cookie{
{Name: HttpOnlyCookieName, Value: "34567890", HttpOnly: true, Raw: "wt-http-token-cookie=34567890; HttpOnly"},
{Name: JsVisibleCookieName, Value: "t_abc_12", Raw: "wt-js-token-cookie=t_abc_12"},
})
}
@@ -4,30 +4,66 @@ import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/hashicorp/boundary/internal/errors"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/observability/event"
authtokenpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"
)
const (
HttpOnlyCookieName = "wt-http-token-cookie"
JsVisibleCookieName = "wt-js-token-cookie"
tokenTypeField = "token_type"
finalRedirectUrlField = "final_redirect_url"
statusField = "status"
HttpOnlyCookieName = "wt-http-token-cookie"
JsVisibleCookieName = "wt-js-token-cookie"
tokenTypeField = "token_type"
finalRedirectUrlField = "final_redirect_url"
statusField = "status"
StatusCodeHeader = "x-http-code"
statusCodeMetadataHeader = "Grpc-Metadata-X-Http-Code"
)
func OutgoingInterceptor(ctx context.Context, w http.ResponseWriter, m proto.Message) error {
const op = "handlers.OutgoingInterceptor"
m = m.ProtoReflect().Interface()
if !m.ProtoReflect().IsValid() {
// This would be the case if it's a nil pointer
w.WriteHeader(http.StatusNoContent)
return nil
// SetStatusCode allows a grpc service handler to set the outgoing http status
// code
func SetStatusCode(ctx context.Context, code int) error {
const op = "handlers.SetHttpStatusCode"
if http.StatusText(code) == "" {
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown status code: %d", code))
}
if err := grpc.SetHeader(ctx, metadata.Pairs(StatusCodeHeader, strconv.Itoa(code))); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Internal))
}
return nil
}
// OutgoingResponseFilter is a gRPC gateway WithForwardResponseOption. It's
// basically a filter that can manipulate the http response and has acesss to
// the outgoing proto msg
func OutgoingResponseFilter(ctx context.Context, w http.ResponseWriter, m proto.Message) error {
const op = "handlers.OutgoingResponseFilter"
if md, ok := runtime.ServerMetadataFromContext(ctx); ok {
// set http status codes based on metadata set by the grpc service
if statusCodes := md.HeaderMD.Get(StatusCodeHeader); len(statusCodes) > 0 {
defer func() {
// delete the headers to not expose any grpc-metadata in http response
delete(md.HeaderMD, StatusCodeHeader)
delete(w.Header(), statusCodeMetadataHeader)
}()
lastStatus, err := strconv.Atoi(statusCodes[len(statusCodes)-1])
if err != nil {
return fmt.Errorf("%s: unable to convert status code %s: %w", op, statusCodes[len(statusCodes)-1], err)
}
w.WriteHeader(lastStatus)
return nil
}
}
// add the outgoing resp proto. See: common.flushGatedEvents is where the
@@ -0,0 +1,72 @@
package handlers
import (
"context"
"net/http"
"net/http/httptest"
"testing"
emptypb "github.com/hashicorp/boundary/internal/gen/controller/api"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
)
func TestOutgoingSplitCookie(t *testing.T) {
rec := httptest.NewRecorder()
attrs, err := ProtoToStruct(&pb.AuthToken{Token: "t_abc_1234567890"})
require.NoError(t, err)
attrs.GetFields()["token_type"] = structpb.NewStringValue("cookie")
require.NoError(t, OutgoingResponseFilter(context.Background(), rec, &pbs.AuthenticateResponse{Attributes: attrs}))
assert.ElementsMatch(t, rec.Result().Cookies(), []*http.Cookie{
{Name: HttpOnlyCookieName, Value: "34567890", HttpOnly: true, Raw: "wt-http-token-cookie=34567890; HttpOnly"},
{Name: JsVisibleCookieName, Value: "t_abc_12", Raw: "wt-js-token-cookie=t_abc_12"},
})
}
func TestOutgoingResponseFilter_StatusCode(t *testing.T) {
tests := []struct {
name string
md runtime.ServerMetadata
msg proto.Message
wantErr bool
wantStatusCode int
wantContent bool
}{
{
name: "204",
md: runtime.ServerMetadata{HeaderMD: metadata.Pairs(StatusCodeHeader, "204")},
wantStatusCode: 204,
},
{
name: "418",
md: runtime.ServerMetadata{HeaderMD: metadata.Pairs(StatusCodeHeader, "418")},
wantStatusCode: 418,
wantContent: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
rec := httptest.NewRecorder()
ctx := runtime.NewServerMetadataContext(context.Background(), tt.md)
err := OutgoingResponseFilter(ctx, rec, &emptypb.EmptyResponse{})
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
resp := rec.Result()
if tt.wantStatusCode != 0 {
assert.Equal(tt.wantStatusCode, resp.StatusCode)
}
})
}
}
@@ -12,6 +12,7 @@ import (
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
@@ -211,15 +212,15 @@ func TestList_Self(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
// Setup the auth request information
req := httptest.NewRequest("GET", fmt.Sprintf("http://127.0.0.1/v1/sessions?scope_id=%s", pWithSessions.GetPublicId()), nil)
requestInfo := auth.RequestInfo{
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
TokenFormat: auth.AuthTokenTypeBearer,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: tc.requester.GetPublicId(),
Token: tc.requester.GetToken(),
}
ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, requestInfo)
ctx := auth.NewVerifierContext(context.Background(), iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
got, err := s.ListSessions(ctx, &pbs.ListSessionsRequest{ScopeId: pWithSessions.GetPublicId()})
require.NoError(t, err)
assert.Equal(t, tc.count, len(got.GetItems()), got.GetItems())
@@ -17,6 +17,7 @@ import (
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/db"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
spbs "github.com/hashicorp/boundary/internal/gen/controller/servers/services"
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/iam"
@@ -2500,9 +2501,9 @@ func TestAuthorizeSession(t *testing.T) {
atRepoFn,
serversRepoFn,
kms,
auth.RequestInfo{
&authpb.RequestInfo{
Token: at.GetToken(),
TokenFormat: auth.AuthTokenTypeBearer,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: at.GetPublicId(),
})
@@ -2674,9 +2675,9 @@ func TestAuthorizeSession_Errors(t *testing.T) {
atRepoFn,
serversRepoFn,
kms,
auth.RequestInfo{
&authpb.RequestInfo{
Token: at.GetToken(),
TokenFormat: auth.AuthTokenTypeBearer,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: at.GetPublicId(),
})
r := iam.TestRole(t, conn, proj.GetPublicId())
@@ -15,10 +15,10 @@ import (
func errorIncludesFields(t *testing.T, err error, wantFields []string) {
t.Helper()
var apiErr *apiError
var apiErr *ApiError
require.True(t, errors.As(err, &apiErr))
var gotFields []string
for _, d := range apiErr.inner.GetDetails().GetRequestFields() {
for _, d := range apiErr.Inner.GetDetails().GetRequestFields() {
gotFields = append(gotFields, d.GetName())
}
assert.ElementsMatch(t, gotFields, wantFields)
+207
View File
@@ -0,0 +1,207 @@
package controller
import (
"context"
"fmt"
"net/http"
"reflect"
"github.com/hashicorp/boundary/internal/errors"
pb "github.com/hashicorp/boundary/internal/gen/controller/api"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
pberrors "github.com/hashicorp/boundary/internal/gen/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/servers/controller/auth"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/mr-tron/base58"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
const (
requestInfoMdKey = "request-info"
// domainErrHeader defines an http header for encoded domain errors from the
// grpc server.
domainErrHeader = "x-domain-err"
// apiErrHeader defines an http header for encoded api errors from the
// grpc server.
apiErrHeader = "x-api-err"
)
// requestCtxInterceptor creates an unary server interceptor that pulls grpc
// metadata into a ctx for the request. The metadata must be set in an upstream
// http handler/middleware by marshalling a RequestInfo protobuf into the
// requestInfoMdKey header (see: controller.wrapHandlerWithCommonFuncs).
func requestCtxInterceptor(
ctx context.Context,
iamRepoFn common.IamRepoFactory,
authTokenRepoFn common.AuthTokenRepoFactory,
serversRepoFn common.ServersRepoFactory,
kms *kms.Kms,
ticket string,
) (grpc.UnaryServerInterceptor, error) {
const op = "controller.requestCtxInterceptor"
if iamRepoFn == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing iam repo function")
}
if authTokenRepoFn == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth token repo function")
}
if serversRepoFn == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing servers repo function")
}
if kms == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing kms")
}
if ticket == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing ticket")
}
// Authorization unary interceptor function to handle authorize per RPC call
return func(interceptorCtx context.Context,
req interface{},
_ *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
md, ok := metadata.FromIncomingContext(interceptorCtx)
if !ok {
return nil, errors.New(interceptorCtx, errors.Internal, op, "No metadata")
}
values := md.Get(requestInfoMdKey)
if len(values) == 0 {
return nil, errors.New(interceptorCtx, errors.Internal, op, "Missing request metadata")
}
if len(values) > 1 {
return nil, errors.New(interceptorCtx, errors.Internal, op, fmt.Sprintf("expected 1 value for %s metadata and got %d", requestInfoMdKey, len(values)))
}
decoded, err := base58.FastBase58Decoding(values[0])
if err != nil {
return nil, errors.Wrap(interceptorCtx, err, op, errors.WithCode(errors.Internal), errors.WithMsg("unable to decode request info"))
}
var requestInfo authpb.RequestInfo
if err := proto.Unmarshal(decoded, &requestInfo); err != nil {
return nil, errors.Wrap(interceptorCtx, err, op, errors.WithCode(errors.Internal), errors.WithMsg("unable to unmarshal request info"))
}
switch {
case requestInfo.Ticket == "":
return nil, errors.New(interceptorCtx, errors.Internal, op, "Invalid context (missing ticket)")
case requestInfo.Ticket != ticket:
return nil, errors.New(interceptorCtx, errors.Internal, op, "Invalid context (bad ticket)")
}
interceptorCtx = auth.NewVerifierContext(interceptorCtx, iamRepoFn, authTokenRepoFn, serversRepoFn, kms, &requestInfo)
// Add general request information to the context. The information from
// the auth verifier context is pretty specifically curated to
// authentication/authorization verification so this is more
// general-purpose.
//
// We could use requests.NewRequestContext but this saves an immediate
// lookup.
interceptorCtx = context.WithValue(interceptorCtx, requests.ContextRequestInformationKey, &requests.RequestContext{
Path: requestInfo.Path,
Method: requestInfo.Method,
})
// Calls the handler
h, err := handler(interceptorCtx, req)
return h, err // not convinced we want to wrap every error and turn them into domain errors...
}, nil
}
func errorInterceptor(
_ context.Context,
) grpc.UnaryServerInterceptor {
const op = "controller.errorInterceptor"
return func(interceptorCtx context.Context,
req interface{},
_ *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
// call the handler...
h, handlerErr := handler(interceptorCtx, req)
// if there's an error and it's a domain err, then encode it into the
if handlerErr != nil {
var domainErr *errors.Err
isDomainErr := errors.As(handlerErr, &domainErr)
if isDomainErr {
pbErr := errors.ToPbErrors(domainErr)
var buf []byte
var err error
if buf, err = proto.Marshal(pbErr); err != nil {
return h, status.Errorf(codes.Internal, "unable to marshal domain error: %s", err)
}
if err := grpc.SetHeader(interceptorCtx, metadata.Pairs(domainErrHeader, base58.FastBase58Encoding(buf))); err != nil {
return h, status.Errorf(codes.Internal, "unable to set domain error header: %s", err)
}
return h, handlerErr
}
var apiErr *handlers.ApiError
isApiError := errors.As(handlerErr, &apiErr)
if isApiError {
pbErr := &pberrors.ApiError{
ApiError: apiErr.Inner,
Status: apiErr.Status,
}
var buf []byte
var err error
if buf, err = proto.Marshal(pbErr); err != nil {
return h, status.Errorf(codes.Internal, "unable to marshal api error: %s", err)
}
if err := grpc.SetHeader(interceptorCtx, metadata.Pairs(apiErrHeader, base58.FastBase58Encoding(buf))); err != nil {
return h, status.Errorf(codes.Internal, "unable to set api error header: %s", err)
}
return h, handlerErr
}
}
return h, handlerErr
}
}
func statusCodeInterceptor(
_ context.Context,
) grpc.UnaryServerInterceptor {
const op = "controller.statusCodeInterceptor"
return func(interceptorCtx context.Context,
req interface{},
_ *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
// call the handler...
h, handlerErr := handler(interceptorCtx, req)
// if a service handler returns nil, nil then we want to single a 204
// response to the proxy with no resp msg
if isNil(h) && handlerErr == nil {
if err := handlers.SetStatusCode(interceptorCtx, http.StatusNoContent); err != nil {
return &pb.EmptyResponse{}, err
}
return &pb.EmptyResponse{}, nil
}
return h, handlerErr
}
}
func isNil(i interface{}) bool {
if i == nil {
return true
}
switch reflect.TypeOf(i).Kind() {
case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
return reflect.ValueOf(i).IsNil()
}
return false
}
@@ -0,0 +1,455 @@
package controller
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
pb_api "github.com/hashicorp/boundary/internal/gen/controller/api"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
pberrors "github.com/hashicorp/boundary/internal/gen/errors"
"github.com/hashicorp/boundary/internal/gen/testing/interceptor"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/servers"
"github.com/hashicorp/boundary/internal/servers/controller/auth"
"github.com/hashicorp/boundary/internal/servers/controller/common"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/mr-tron/base58"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"
)
func Test_requestCtxInterceptor(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
atRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(rw, rw, kmsCache)
}
serversRepoFn := func() (*servers.Repository, error) {
return servers.NewRepository(rw, rw, kmsCache)
}
validGatewayTicket := "valid-ticket"
o, _ := iam.TestScopes(t, iamRepo)
at := authtoken.TestAuthToken(t, conn, kmsCache, o.GetPublicId())
encToken, err := authtoken.EncryptToken(context.Background(), kmsCache, o.GetPublicId(), at.GetPublicId(), at.GetToken())
require.NoError(t, err)
tokValue := at.GetPublicId() + "_" + encToken
newReqCtx := func(gwTicket string) context.Context {
req := httptest.NewRequest("GET", "http://127.0.0.1/v1/scopes/o_1", nil)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", tokValue))
// Add values for authn/authz checking
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
}
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = auth.GetTokenFromRequest(context.TODO(), kmsCache, req)
requestInfo.Ticket = gwTicket // allows the grpc-gateway to verify the request info came from it's in-memory companion http proxy
marshalledRequestInfo, err := proto.Marshal(&requestInfo)
require.NoError(t, err)
md := metadata.Pairs(requestInfoMdKey, base58.FastBase58Encoding(marshalledRequestInfo))
mdCtx := metadata.NewIncomingContext(context.Background(), md)
md, ok := metadata.FromIncomingContext(mdCtx)
require.True(t, ok)
require.NotNil(t, md)
return mdCtx
}
factoryCtx := context.Background()
returnCtxHandler := func(ctx context.Context, req interface{}) (interface{}, error) {
return ctx, nil
}
tests := []struct {
name string
requestCtx context.Context
iamRepoFn common.IamRepoFactory
authTokenRepoFn common.AuthTokenRepoFactory
serversRepoFn common.ServersRepoFactory
kms *kms.Kms
ticket string
wantFactoryErr bool
wantFactoryErrMatch *errors.Template
wantFactoryErrContains string
wantRequestErr bool
wantRequestErrMatch *errors.Template
wantRequestErrContains string
}{
{
name: "missing-iam-repo",
requestCtx: newReqCtx(validGatewayTicket),
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
wantFactoryErr: true,
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
wantFactoryErrContains: "missing iam repo",
},
{
name: "missing-at-repo",
requestCtx: newReqCtx(validGatewayTicket),
iamRepoFn: iamRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
wantFactoryErr: true,
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
wantFactoryErrContains: "missing auth token repo",
},
{
name: "missing-servers-repo",
requestCtx: newReqCtx(validGatewayTicket),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
kms: kmsCache,
wantFactoryErr: true,
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
wantFactoryErrContains: "missing servers repo function",
},
{
name: "missing-kms",
requestCtx: newReqCtx(validGatewayTicket),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
wantFactoryErr: true,
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
wantFactoryErrContains: "missing kms",
},
{
name: "missing-factory-ticket",
requestCtx: newReqCtx(validGatewayTicket),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
wantFactoryErr: true,
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
wantFactoryErrContains: "missing ticket",
},
{
name: "missing-metadata",
requestCtx: context.Background(),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
ticket: validGatewayTicket,
wantRequestErr: true,
wantRequestErrMatch: errors.T(errors.Internal),
wantRequestErrContains: "No metadata",
},
{
name: "missing-request-info-metadata",
requestCtx: func() context.Context {
md := metadata.Pairs("greeter-md", "hello")
return metadata.NewIncomingContext(context.Background(), md)
}(),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
ticket: validGatewayTicket,
wantRequestErr: true,
wantRequestErrMatch: errors.T(errors.Internal),
wantRequestErrContains: "Missing request metadata",
},
{
name: "too-many-request-info-metadata",
requestCtx: func() context.Context {
md := metadata.Pairs(requestInfoMdKey, "first", requestInfoMdKey, "second")
return metadata.NewIncomingContext(context.Background(), md)
}(),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
ticket: validGatewayTicket,
wantRequestErr: true,
wantRequestErrMatch: errors.T(errors.Internal),
wantRequestErrContains: "expected 1 value",
},
{
name: "request-info-metadata-not-encoded",
requestCtx: func() context.Context {
md := metadata.Pairs(requestInfoMdKey, "hello")
return metadata.NewIncomingContext(context.Background(), md)
}(),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
ticket: validGatewayTicket,
wantRequestErr: true,
wantRequestErrMatch: errors.T(errors.Internal),
wantRequestErrContains: "unable to decode request info",
},
{
name: "request-info-metadata-not-proto",
requestCtx: func() context.Context {
md := metadata.Pairs(requestInfoMdKey, base58.FastBase58Encoding([]byte("hello")))
return metadata.NewIncomingContext(context.Background(), md)
}(),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
ticket: validGatewayTicket,
wantRequestErr: true,
wantRequestErrMatch: errors.T(errors.Internal),
wantRequestErrContains: "unable to unmarshal request info",
},
{
name: "request-info-metadata-not-proto",
requestCtx: func() context.Context {
md := metadata.Pairs(requestInfoMdKey, base58.FastBase58Encoding([]byte("hello")))
return metadata.NewIncomingContext(context.Background(), md)
}(),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
ticket: validGatewayTicket,
wantRequestErr: true,
wantRequestErrMatch: errors.T(errors.Internal),
wantRequestErrContains: "unable to unmarshal request info",
},
{
name: "missing-request-ticket",
requestCtx: newReqCtx(""),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
ticket: "validGatewayTicket",
wantRequestErr: true,
wantRequestErrMatch: errors.T(errors.Internal),
wantRequestErrContains: "Invalid context (missing ticket)",
},
{
name: "bad-ticket",
requestCtx: newReqCtx("bad-ticket"),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
ticket: "validGatewayTicket",
wantRequestErr: true,
wantRequestErrMatch: errors.T(errors.Internal),
wantRequestErrContains: "Invalid context (bad ticket)",
},
{
name: "valid",
requestCtx: newReqCtx(validGatewayTicket),
iamRepoFn: iamRepoFn,
authTokenRepoFn: atRepoFn,
serversRepoFn: serversRepoFn,
kms: kmsCache,
ticket: validGatewayTicket,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
interceptor, err := requestCtxInterceptor(factoryCtx, tt.iamRepoFn, tt.authTokenRepoFn, tt.serversRepoFn, tt.kms, tt.ticket)
if tt.wantFactoryErr {
require.Error(err)
assert.Nil(interceptor)
if tt.wantFactoryErrMatch != nil {
assert.Truef(errors.Match(tt.wantFactoryErrMatch, err), "want err code: %q got: %q", tt.wantFactoryErrMatch.Code, err)
}
if tt.wantFactoryErrContains != "" {
assert.Contains(err.Error(), tt.wantFactoryErrContains)
}
return
}
require.NoError(err)
assert.NotNil(interceptor)
info := &grpc.UnaryServerInfo{
FullMethod: "FakeMethod",
}
retCtx, err := interceptor(tt.requestCtx, nil, info, returnCtxHandler)
if tt.wantRequestErr {
require.Error(err)
assert.Nil(retCtx)
if tt.wantRequestErrMatch != nil {
assert.Truef(errors.Match(tt.wantRequestErrMatch, err), "want err code: %q got: %q", tt.wantRequestErrMatch.Code, err)
}
if tt.wantRequestErrContains != "" {
assert.Contains(err.Error(), tt.wantRequestErrContains)
}
return
}
require.NoError(err)
verifyResults := auth.Verify(retCtx.(context.Context))
assert.NotEmpty(verifyResults)
})
}
}
func Test_errorInterceptor(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
factoryCtx context.Context
wantRequestErr bool
wantDomainErr bool
wantApiErr bool
}{
{
name: "domain-error",
factoryCtx: ctx,
wantRequestErr: true,
wantDomainErr: true,
},
{
name: "api-error",
factoryCtx: ctx,
wantRequestErr: true,
wantApiErr: true,
},
{
name: "success",
factoryCtx: ctx,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
require.Falsef(tt.wantApiErr && tt.wantDomainErr, "tests can't require both wantApiErr and wantDomainErr")
errInterceptor := errorInterceptor(tt.factoryCtx)
client := startTestGreeterService(t, &testGreeter{}, errInterceptor)
var header metadata.MD
resp, err := client.SayHello(
context.Background(),
&interceptor.SayHelloRequest{Name: tt.name},
grpc.Header(&header),
)
if tt.wantRequestErr {
require.Error(err)
domainHdr := header.Get(domainErrHeader)
apiErrHdr := header.Get(apiErrHeader)
require.Error(err)
assert.Nil(resp)
if tt.wantDomainErr {
require.Len(domainHdr, 1)
assert.Len(apiErrHdr, 0)
decoded, err := base58.FastBase58Decoding(domainHdr[0])
require.NoError(err)
var pbErr pberrors.Err
err = proto.Unmarshal(decoded, &pbErr)
require.NoError(err)
}
if tt.wantApiErr {
require.Len(apiErrHdr, 1)
assert.Len(domainHdr, 0)
decoded, err := base58.FastBase58Decoding(apiErrHdr[0])
require.NoError(err)
var pbErr pberrors.ApiError
err = proto.Unmarshal(decoded, &pbErr)
require.NoError(err)
}
return
}
require.NoError(err)
})
}
}
func Test_statusCodeInterceptor(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
wantStatusCode int
wantErr bool
}{
{
name: "nil-nil",
wantStatusCode: http.StatusNoContent,
},
{
name: "nil-err",
wantErr: true,
},
{
name: "hello",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
statusInterceptor := statusCodeInterceptor(ctx)
client := startTestGreeterService(t, &testGreeter{}, statusInterceptor)
var header metadata.MD
_, err := client.SayHello(
context.Background(),
&interceptor.SayHelloRequest{Name: tt.name},
grpc.Header(&header),
)
if tt.wantErr {
assert.Error(err)
}
statusHdr := header.Get(handlers.StatusCodeHeader)
if tt.wantStatusCode > 0 {
require.Len(statusHdr, 1)
code, err := strconv.Atoi(statusHdr[0])
require.NoError(err)
assert.Equal(tt.wantStatusCode, code)
} else {
require.Len(statusHdr, 0)
}
})
}
}
type testGreeter struct {
interceptor.UnimplementedGreeterServiceServer
}
func (g *testGreeter) SayHello(ctx context.Context, req *interceptor.SayHelloRequest) (*interceptor.SayHelloResponse, error) {
const op = "SayHello"
switch req.GetName() {
case "api-error":
return &interceptor.SayHelloResponse{}, &handlers.ApiError{
Status: http.StatusInternalServerError,
Inner: &pb_api.Error{
Kind: "internal",
Op: op,
Message: "api error msg",
},
}
case "domain-error":
return &interceptor.SayHelloResponse{Message: "hello"}, errors.New(ctx, errors.Internal, op, "domain error msg")
case "nil-nil":
return nil, nil
case "nil-err":
return nil, errors.New(ctx, errors.Internal, op, "nil response error msg")
default:
return &interceptor.SayHelloResponse{Message: "hello"}, nil
}
}
+28 -1
View File
@@ -12,6 +12,7 @@ import (
"sync"
"time"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/cmd/base"
pbs "github.com/hashicorp/boundary/internal/gen/controller/servers/services"
"github.com/hashicorp/boundary/internal/libs/alpnmux"
@@ -20,10 +21,16 @@ import (
"google.golang.org/grpc"
)
func (c *Controller) startListeners() error {
func (c *Controller) startListeners(ctx context.Context) error {
servers := make([]func(), 0, len(c.conf.Listeners))
configureForAPI := func(ln *base.ServerListener) error {
var err error
if c.gatewayServer, c.gatewayTicket, err = newGatewayServer(ctx, c.IamRepoFn, c.AuthTokenRepoFn, c.ServersRepoFn, c.kms); err != nil {
return err
}
c.gatewayMux = newGatewayMux()
handler, err := c.handler(HandlerProperties{
ListenerConfig: ln.Config,
CancelCtx: c.baseContext,
@@ -134,6 +141,11 @@ func (c *Controller) startListeners() error {
return nil
}
c.gatewayListener, _ = newGatewayListener()
servers = append(servers, func() {
go c.gatewayServer.Serve(c.gatewayListener)
})
for _, ln := range c.conf.Listeners {
var err error
for _, purpose := range ln.Config.Purpose {
@@ -184,6 +196,21 @@ func (c *Controller) stopListeners(serversOnly bool) error {
}
}()
}
if c.gatewayServer != nil {
serverWg.Add(1)
go func() {
defer serverWg.Done()
shutdownKill, shutdownKillCancel := context.WithTimeout(c.baseContext, globals.DefaultMaxRequestDuration)
defer shutdownKillCancel()
go func() {
<-shutdownKill.Done()
c.gatewayServer.Stop()
}()
c.gatewayServer.GracefulStop()
}()
}
serverWg.Wait()
if serversOnly {
return nil
+41
View File
@@ -10,6 +10,7 @@ import (
"testing"
"time"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/boundary/api/authtokens"
@@ -18,6 +19,7 @@ import (
"github.com/hashicorp/boundary/internal/cmd/config"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/schema"
"github.com/hashicorp/boundary/internal/gen/testing/interceptor"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/intglobals"
"github.com/hashicorp/boundary/internal/kms"
@@ -27,6 +29,9 @@ import (
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-secure-stdlib/base62"
"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
)
const (
@@ -716,3 +721,39 @@ func (tc *TestController) WaitForNextWorkerStatusUpdate(workerId string) error {
event.WriteSysEvent(ctx, op, "waiting for next status report from worker received successfully", "worker", workerId)
return nil
}
// startTestGreeterService is intended to facilitate the testing of
// interceptors. You provide a greeter service that produces appropriate
// responses to test your interceptor(s). This test function will start up a
// test greeter service wrapped with the specified interceptors and return an
// initialized client for the service. The test service will be stopped/cleaned
// up after the test (or subtests) have completed.
func startTestGreeterService(t *testing.T, greeter interceptor.GreeterServiceServer, interceptors ...grpc.UnaryServerInterceptor) interceptor.GreeterServiceClient {
t.Helper()
require := require.New(t)
dialCtx := context.Background()
buffer := 1024 * 1024
listener := bufconn.Listen(buffer)
s := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(interceptors...)),
)
interceptor.RegisterGreeterServiceServer(s, greeter)
go func() {
err := s.Serve(listener)
require.NoError(err)
}()
conn, _ := grpc.DialContext(dialCtx, "", grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return listener.Dial()
}), grpc.WithInsecure())
t.Cleanup(func() {
listener.Close()
s.Stop()
})
client := interceptor.NewGreeterServiceClient(conn)
return client
}
@@ -22,3 +22,11 @@ function strip_all() {
echo "$1" | tr -d '"' | tr -d '\'\'
}
function has_status_code() {
local json=$1
local code=$2
if [ echo "$json"|jq -c ".status_code == $code" ]; then
return 1
fi
}
+1
View File
@@ -59,6 +59,7 @@ export NEW_GROUP='test'
local gid=$(group_id $NEW_GROUP)
run delete_group $gid
echo "$output"
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
@@ -44,6 +44,7 @@ export NEW_HOST_CATALOG='test'
local hid=$(host_catalog_id $NEW_HOST_CATALOG $DEFAULT_P_ID)
run delete_host_catalog $hid
echo "$output"
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
@@ -58,6 +58,7 @@ export NEW_HOST_SET='test'
local hsid=$(host_set_id $NEW_HOST_SET $DEFAULT_HOST_CATALOG)
run delete_host_set $hsid
echo "$output"
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
+1
View File
@@ -44,6 +44,7 @@ export NEW_HOST='test'
local hid=$(host_id $NEW_HOST $DEFAULT_HOST_CATALOG)
run delete_host $hid
echo "$output"
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
+1
View File
@@ -102,6 +102,7 @@ export NEW_GRANT='id=*;type=*;actions=create,read,update,delete,list'
local rid=$(role_id $NEW_ROLE $DEFAULT_GLOBAL)
run delete_role $rid
echo "$output"
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
+2
View File
@@ -64,6 +64,7 @@ export NEW_PROJECT='test_project'
local sid=$(scope_id $NEW_PROJECT $parent)
run delete_scope $sid
echo "$output"
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
@@ -79,6 +80,7 @@ export NEW_PROJECT='test_project'
local sid=$(scope_id $NEW_ORG $DEFAULT_GLOBAL)
run delete_scope $sid
echo "$output"
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
+1
View File
@@ -92,6 +92,7 @@ load _helpers
@test "boundary/target: default user can delete target" {
local id=$(target_id_from_name $DEFAULT_P_ID $TGT_NAME)
run delete_target $id
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
+4 -2
View File
@@ -68,7 +68,9 @@ export NEW_USER='test'
@test "boundary/user: can delete $NEW_USER user" {
login $DEFAULT_LOGIN
local uid=$(user_id $NEW_USER)
run delete_user $uid
run delete_user $uid
echo $output
run has_status_code "$output" "204"
[ "$status" -eq 0 ]
}
@@ -82,4 +84,4 @@ export NEW_USER='test'
local aid=$(account_id $NEW_USER)
run delete_account $aid
[ "$status" -eq 0 ]
}
}