Implement the Auth interface, with JWT and service implementations.

* Update Auth Interface

* Define Auth Service Implementation

* Support Service Auth

* Add Auth Service Proto

* Remove erronious files

* Implement Auth Service Package

* Update Auth Interface

* Update Auth Interface. Add Validate, remove Add/Remove roles

* Make Revoke interface more explicit

* Refactor serializing and deserializing service accounts

* Fix srv name & update interface to be more explicit

* Require jwt public key for auth

* Rename Variables (Resource.ID => Resource.Name & ServiceAccount => Account)

* Implement JWT Auth Package

* Remove parent, add ID

* Update auth imports to v2. Add String() to auth interface
This commit is contained in:
ben-toogood 2020-02-03 08:16:02 +00:00 committed by GitHub
parent 449bcb46fe
commit d621548120
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1103 additions and 26 deletions

View File

@ -7,34 +7,44 @@ import (
// Auth providers authentication and authorization // Auth providers authentication and authorization
type Auth interface { type Auth interface {
// Generate a new auth token // String to identify the package
Generate(string) (*Token, error) String() string
// Revoke an authorization token // Init the auth package
Revoke(*Token) error Init(opts ...Option) error
// Grant access to a resource // Generate a new auth Account
Grant(*Token, *Service) error Generate(id string, opts ...GenerateOption) (*Account, error)
// Verify a token can access a resource // Revoke an authorization Account
Verify(*Token, *Service) error Revoke(token string) error
// Validate an account token
Validate(token string) (*Account, error)
} }
// Service is some thing to provide access to // Resource is an entity such as a user or
type Service struct { type Resource struct {
// Name of the resource // Name of the resource
Name string Name string
// Endpoint is the specific endpoint // Type of resource, e.g.
Endpoint string Type string
} }
// Token providers by an auth provider // Role an account has
type Token struct { type Role struct {
// Unique token id Name string
Resource *Resource
}
// Account provided by an auth provider
type Account struct {
// ID of the account (UUID or email)
Id string `json: "id"` Id string `json: "id"`
// Time of token creation // Token used to authenticate
Token string `json: "token"`
// Time of Account creation
Created time.Time `json:"created"` Created time.Time `json:"created"`
// Time of token expiry // Time of Account expiry
Expiry time.Time `json:"expiry"` Expiry time.Time `json:"expiry"`
// Roles associated with the token // Roles associated with the Account
Roles []string `json:"roles"` Roles []*Role `json:"roles"`
// Any other associated metadata // Any other associated metadata
Metadata map[string]string `json:"metadata"` Metadata map[string]string `json:"metadata"`
} }

34
auth/default.go Normal file
View File

@ -0,0 +1,34 @@
package auth
var (
DefaultAuth Auth = new(noop)
)
type noop struct {
options Options
}
// String name of implementation
func (a *noop) String() string {
return "noop"
}
// Init the svc
func (a *noop) Init(...Option) error {
return nil
}
// Generate a new auth Account
func (a *noop) Generate(id string, ops ...GenerateOption) (*Account, error) {
return nil, nil
}
// Revoke an authorization Account
func (a *noop) Revoke(token string) error {
return nil
}
// Validate a account token
func (a *noop) Validate(token string) (*Account, error) {
return nil, nil
}

106
auth/jwt/jwt.go Normal file
View File

@ -0,0 +1,106 @@
package jwt
import (
"errors"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/micro/go-micro/v2/auth"
)
// ErrInvalidPrivateKey is returned when the service provided an invalid private key
var ErrInvalidPrivateKey = errors.New("An invalid private key was provided")
// ErrEncodingToken is returned when the service encounters an error during encoding
var ErrEncodingToken = errors.New("An error occured while encoding the JWT")
// ErrInvalidToken is returned when the token provided is not valid
var ErrInvalidToken = errors.New("An invalid token was provided")
// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
svc := new(svc)
svc.Init(opts...)
return svc
}
// svc is the JWT implementation of the Auth interface
type svc struct {
options auth.Options
}
func (s *svc) String() string {
return "jwt"
}
func (s *svc) Init(opts ...auth.Option) error {
for _, o := range opts {
o(&s.options)
}
return nil
}
// AuthClaims to be encoded in the JWT
type AuthClaims struct {
Id string `json:"id"`
Roles []*auth.Role `json:"roles"`
Metadata map[string]string `json:"metadata"`
jwt.StandardClaims
}
// Generate a new JWT
func (s *svc) Generate(id string, ops ...auth.GenerateOption) (*auth.Account, error) {
key, err := jwt.ParseRSAPrivateKeyFromPEM(s.options.PrivateKey)
if err != nil {
return nil, ErrEncodingToken
}
options := auth.NewGenerateOptions(ops...)
account := jwt.NewWithClaims(jwt.SigningMethodRS256, AuthClaims{
id, options.Roles, options.Metadata, jwt.StandardClaims{
Subject: "TODO",
ExpiresAt: time.Now().Add(time.Hour * 24).Unix(),
},
})
token, err := account.SignedString(key)
if err != nil {
return nil, err
}
return &auth.Account{
Id: id,
Token: token,
Roles: options.Roles,
Metadata: options.Metadata,
}, nil
}
// Revoke an authorization account
func (s *svc) Revoke(token string) error {
return nil
}
// Validate a JWT
func (s *svc) Validate(token string) (*auth.Account, error) {
res, err := jwt.ParseWithClaims(token, &AuthClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwt.ParseRSAPublicKeyFromPEM(s.options.PublicKey)
})
if err != nil {
return nil, err
}
if !res.Valid {
return nil, ErrInvalidToken
}
claims := res.Claims.(*AuthClaims)
return &auth.Account{
Id: claims.Id,
Metadata: claims.Metadata,
Roles: claims.Roles,
}, nil
}

57
auth/options.go Normal file
View File

@ -0,0 +1,57 @@
package auth
import (
b64 "encoding/base64"
)
type Options struct {
PublicKey []byte
PrivateKey []byte
}
type Option func(o *Options)
// PublicKey is the JWT public key
func PublicKey(key string) Option {
return func(o *Options) {
o.PublicKey, _ = b64.StdEncoding.DecodeString(key)
}
}
// PrivateKey is the JWT private key
func PrivateKey(key string) Option {
return func(o *Options) {
o.PrivateKey, _ = b64.StdEncoding.DecodeString(key)
}
}
type GenerateOptions struct {
Metadata map[string]string
Roles []*Role
}
type GenerateOption func(o *GenerateOptions)
// Metadata for the generated account
func Metadata(md map[string]string) func(o *GenerateOptions) {
return func(o *GenerateOptions) {
o.Metadata = md
}
}
// Roles for the generated account
func Roles(rs []*Role) func(o *GenerateOptions) {
return func(o *GenerateOptions) {
o.Roles = rs
}
}
// NewGenerateOptions from a slice of options
func NewGenerateOptions(opts ...GenerateOption) GenerateOptions {
var options GenerateOptions
for _, o := range opts {
o(&options)
}
return options
}

View File

@ -0,0 +1,466 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: auth/service/proto/auth.proto
package go_micro_auth
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Account struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"`
Created int64 `protobuf:"varint,3,opt,name=created,proto3" json:"created,omitempty"`
Expiry int64 `protobuf:"varint,4,opt,name=expiry,proto3" json:"expiry,omitempty"`
Roles []*Role `protobuf:"bytes,5,rep,name=roles,proto3" json:"roles,omitempty"`
Metadata map[string]string `protobuf:"bytes,6,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Account) Reset() { *m = Account{} }
func (m *Account) String() string { return proto.CompactTextString(m) }
func (*Account) ProtoMessage() {}
func (*Account) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{0}
}
func (m *Account) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Account.Unmarshal(m, b)
}
func (m *Account) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Account.Marshal(b, m, deterministic)
}
func (m *Account) XXX_Merge(src proto.Message) {
xxx_messageInfo_Account.Merge(m, src)
}
func (m *Account) XXX_Size() int {
return xxx_messageInfo_Account.Size(m)
}
func (m *Account) XXX_DiscardUnknown() {
xxx_messageInfo_Account.DiscardUnknown(m)
}
var xxx_messageInfo_Account proto.InternalMessageInfo
func (m *Account) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *Account) GetToken() string {
if m != nil {
return m.Token
}
return ""
}
func (m *Account) GetCreated() int64 {
if m != nil {
return m.Created
}
return 0
}
func (m *Account) GetExpiry() int64 {
if m != nil {
return m.Expiry
}
return 0
}
func (m *Account) GetRoles() []*Role {
if m != nil {
return m.Roles
}
return nil
}
func (m *Account) GetMetadata() map[string]string {
if m != nil {
return m.Metadata
}
return nil
}
type Role struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Resource *Resource `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Role) Reset() { *m = Role{} }
func (m *Role) String() string { return proto.CompactTextString(m) }
func (*Role) ProtoMessage() {}
func (*Role) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{1}
}
func (m *Role) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Role.Unmarshal(m, b)
}
func (m *Role) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Role.Marshal(b, m, deterministic)
}
func (m *Role) XXX_Merge(src proto.Message) {
xxx_messageInfo_Role.Merge(m, src)
}
func (m *Role) XXX_Size() int {
return xxx_messageInfo_Role.Size(m)
}
func (m *Role) XXX_DiscardUnknown() {
xxx_messageInfo_Role.DiscardUnknown(m)
}
var xxx_messageInfo_Role proto.InternalMessageInfo
func (m *Role) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *Role) GetResource() *Resource {
if m != nil {
return m.Resource
}
return nil
}
type Resource struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Resource) Reset() { *m = Resource{} }
func (m *Resource) String() string { return proto.CompactTextString(m) }
func (*Resource) ProtoMessage() {}
func (*Resource) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{2}
}
func (m *Resource) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Resource.Unmarshal(m, b)
}
func (m *Resource) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Resource.Marshal(b, m, deterministic)
}
func (m *Resource) XXX_Merge(src proto.Message) {
xxx_messageInfo_Resource.Merge(m, src)
}
func (m *Resource) XXX_Size() int {
return xxx_messageInfo_Resource.Size(m)
}
func (m *Resource) XXX_DiscardUnknown() {
xxx_messageInfo_Resource.DiscardUnknown(m)
}
var xxx_messageInfo_Resource proto.InternalMessageInfo
func (m *Resource) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *Resource) GetType() string {
if m != nil {
return m.Type
}
return ""
}
type GenerateRequest struct {
Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *GenerateRequest) Reset() { *m = GenerateRequest{} }
func (m *GenerateRequest) String() string { return proto.CompactTextString(m) }
func (*GenerateRequest) ProtoMessage() {}
func (*GenerateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{3}
}
func (m *GenerateRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GenerateRequest.Unmarshal(m, b)
}
func (m *GenerateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_GenerateRequest.Marshal(b, m, deterministic)
}
func (m *GenerateRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_GenerateRequest.Merge(m, src)
}
func (m *GenerateRequest) XXX_Size() int {
return xxx_messageInfo_GenerateRequest.Size(m)
}
func (m *GenerateRequest) XXX_DiscardUnknown() {
xxx_messageInfo_GenerateRequest.DiscardUnknown(m)
}
var xxx_messageInfo_GenerateRequest proto.InternalMessageInfo
func (m *GenerateRequest) GetAccount() *Account {
if m != nil {
return m.Account
}
return nil
}
type GenerateResponse struct {
Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *GenerateResponse) Reset() { *m = GenerateResponse{} }
func (m *GenerateResponse) String() string { return proto.CompactTextString(m) }
func (*GenerateResponse) ProtoMessage() {}
func (*GenerateResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{4}
}
func (m *GenerateResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GenerateResponse.Unmarshal(m, b)
}
func (m *GenerateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_GenerateResponse.Marshal(b, m, deterministic)
}
func (m *GenerateResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_GenerateResponse.Merge(m, src)
}
func (m *GenerateResponse) XXX_Size() int {
return xxx_messageInfo_GenerateResponse.Size(m)
}
func (m *GenerateResponse) XXX_DiscardUnknown() {
xxx_messageInfo_GenerateResponse.DiscardUnknown(m)
}
var xxx_messageInfo_GenerateResponse proto.InternalMessageInfo
func (m *GenerateResponse) GetAccount() *Account {
if m != nil {
return m.Account
}
return nil
}
type ValidateRequest struct {
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ValidateRequest) Reset() { *m = ValidateRequest{} }
func (m *ValidateRequest) String() string { return proto.CompactTextString(m) }
func (*ValidateRequest) ProtoMessage() {}
func (*ValidateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{5}
}
func (m *ValidateRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ValidateRequest.Unmarshal(m, b)
}
func (m *ValidateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ValidateRequest.Marshal(b, m, deterministic)
}
func (m *ValidateRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ValidateRequest.Merge(m, src)
}
func (m *ValidateRequest) XXX_Size() int {
return xxx_messageInfo_ValidateRequest.Size(m)
}
func (m *ValidateRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ValidateRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ValidateRequest proto.InternalMessageInfo
func (m *ValidateRequest) GetToken() string {
if m != nil {
return m.Token
}
return ""
}
type ValidateResponse struct {
Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ValidateResponse) Reset() { *m = ValidateResponse{} }
func (m *ValidateResponse) String() string { return proto.CompactTextString(m) }
func (*ValidateResponse) ProtoMessage() {}
func (*ValidateResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{6}
}
func (m *ValidateResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ValidateResponse.Unmarshal(m, b)
}
func (m *ValidateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ValidateResponse.Marshal(b, m, deterministic)
}
func (m *ValidateResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ValidateResponse.Merge(m, src)
}
func (m *ValidateResponse) XXX_Size() int {
return xxx_messageInfo_ValidateResponse.Size(m)
}
func (m *ValidateResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ValidateResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ValidateResponse proto.InternalMessageInfo
func (m *ValidateResponse) GetAccount() *Account {
if m != nil {
return m.Account
}
return nil
}
type RevokeRequest struct {
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RevokeRequest) Reset() { *m = RevokeRequest{} }
func (m *RevokeRequest) String() string { return proto.CompactTextString(m) }
func (*RevokeRequest) ProtoMessage() {}
func (*RevokeRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{7}
}
func (m *RevokeRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RevokeRequest.Unmarshal(m, b)
}
func (m *RevokeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RevokeRequest.Marshal(b, m, deterministic)
}
func (m *RevokeRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_RevokeRequest.Merge(m, src)
}
func (m *RevokeRequest) XXX_Size() int {
return xxx_messageInfo_RevokeRequest.Size(m)
}
func (m *RevokeRequest) XXX_DiscardUnknown() {
xxx_messageInfo_RevokeRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RevokeRequest proto.InternalMessageInfo
func (m *RevokeRequest) GetToken() string {
if m != nil {
return m.Token
}
return ""
}
type RevokeResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RevokeResponse) Reset() { *m = RevokeResponse{} }
func (m *RevokeResponse) String() string { return proto.CompactTextString(m) }
func (*RevokeResponse) ProtoMessage() {}
func (*RevokeResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_21300bfacc51fc2a, []int{8}
}
func (m *RevokeResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RevokeResponse.Unmarshal(m, b)
}
func (m *RevokeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RevokeResponse.Marshal(b, m, deterministic)
}
func (m *RevokeResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_RevokeResponse.Merge(m, src)
}
func (m *RevokeResponse) XXX_Size() int {
return xxx_messageInfo_RevokeResponse.Size(m)
}
func (m *RevokeResponse) XXX_DiscardUnknown() {
xxx_messageInfo_RevokeResponse.DiscardUnknown(m)
}
var xxx_messageInfo_RevokeResponse proto.InternalMessageInfo
func init() {
proto.RegisterType((*Account)(nil), "go.micro.auth.Account")
proto.RegisterMapType((map[string]string)(nil), "go.micro.auth.Account.MetadataEntry")
proto.RegisterType((*Role)(nil), "go.micro.auth.Role")
proto.RegisterType((*Resource)(nil), "go.micro.auth.Resource")
proto.RegisterType((*GenerateRequest)(nil), "go.micro.auth.GenerateRequest")
proto.RegisterType((*GenerateResponse)(nil), "go.micro.auth.GenerateResponse")
proto.RegisterType((*ValidateRequest)(nil), "go.micro.auth.ValidateRequest")
proto.RegisterType((*ValidateResponse)(nil), "go.micro.auth.ValidateResponse")
proto.RegisterType((*RevokeRequest)(nil), "go.micro.auth.RevokeRequest")
proto.RegisterType((*RevokeResponse)(nil), "go.micro.auth.RevokeResponse")
}
func init() { proto.RegisterFile("auth/service/proto/auth.proto", fileDescriptor_21300bfacc51fc2a) }
var fileDescriptor_21300bfacc51fc2a = []byte{
// 429 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x53, 0x4d, 0x6f, 0xd3, 0x40,
0x10, 0xad, 0x3f, 0xe2, 0x98, 0x89, 0xd2, 0x46, 0x03, 0x2a, 0x56, 0x44, 0x21, 0xb2, 0x40, 0x84,
0x8b, 0x83, 0xdc, 0x0b, 0x82, 0x0b, 0x15, 0xa0, 0x9e, 0x2a, 0xa4, 0x3d, 0x70, 0x5f, 0xec, 0x11,
0xb5, 0xe2, 0x78, 0xcd, 0x7a, 0x1d, 0xe1, 0xdf, 0xc0, 0x6f, 0xe5, 0x3f, 0x20, 0xaf, 0xbd, 0x69,
0xea, 0xb4, 0xaa, 0xd4, 0xdb, 0x7c, 0xbc, 0x79, 0xf3, 0xde, 0x68, 0x17, 0xce, 0x78, 0xad, 0xae,
0x57, 0x15, 0xc9, 0x6d, 0x96, 0xd0, 0xaa, 0x94, 0x42, 0x89, 0x55, 0x5b, 0x8a, 0x74, 0x88, 0xd3,
0x5f, 0x22, 0xda, 0x64, 0x89, 0x14, 0x51, 0x5b, 0x0c, 0xff, 0xda, 0x30, 0xbe, 0x48, 0x12, 0x51,
0x17, 0x0a, 0x8f, 0xc1, 0xce, 0xd2, 0xc0, 0x5a, 0x58, 0xcb, 0x27, 0xcc, 0xce, 0x52, 0x7c, 0x06,
0x23, 0x25, 0xd6, 0x54, 0x04, 0xb6, 0x2e, 0x75, 0x09, 0x06, 0x30, 0x4e, 0x24, 0x71, 0x45, 0x69,
0xe0, 0x2c, 0xac, 0xa5, 0xc3, 0x4c, 0x8a, 0xa7, 0xe0, 0xd1, 0x9f, 0x32, 0x93, 0x4d, 0xe0, 0xea,
0x46, 0x9f, 0xe1, 0x3b, 0x18, 0x49, 0x91, 0x53, 0x15, 0x8c, 0x16, 0xce, 0x72, 0x12, 0x3f, 0x8d,
0x6e, 0x49, 0x88, 0x98, 0xc8, 0x89, 0x75, 0x08, 0xfc, 0x0c, 0xfe, 0x86, 0x14, 0x4f, 0xb9, 0xe2,
0x81, 0xa7, 0xd1, 0xaf, 0x07, 0xe8, 0x5e, 0x6c, 0x74, 0xd5, 0xc3, 0xbe, 0x15, 0x4a, 0x36, 0x6c,
0x37, 0x35, 0xff, 0x04, 0xd3, 0x5b, 0x2d, 0x9c, 0x81, 0xb3, 0xa6, 0xa6, 0xb7, 0xd5, 0x86, 0xad,
0xaf, 0x2d, 0xcf, 0x6b, 0x32, 0xbe, 0x74, 0xf2, 0xd1, 0xfe, 0x60, 0x85, 0xdf, 0xc1, 0x6d, 0xd5,
0x20, 0x82, 0x5b, 0xf0, 0x0d, 0xf5, 0x43, 0x3a, 0xc6, 0x73, 0xf0, 0x25, 0x55, 0xa2, 0x96, 0x49,
0x37, 0x38, 0x89, 0x9f, 0x0f, 0x8d, 0xf4, 0x6d, 0xb6, 0x03, 0x86, 0x31, 0xf8, 0xa6, 0x7a, 0x27,
0x29, 0x82, 0xab, 0x9a, 0xd2, 0x28, 0xd1, 0x71, 0xf8, 0x05, 0x4e, 0x2e, 0xa9, 0x20, 0xc9, 0x15,
0x31, 0xfa, 0x5d, 0x53, 0xa5, 0xf0, 0x3d, 0x8c, 0x79, 0xe7, 0x5b, 0x4f, 0x4f, 0xe2, 0xd3, 0xbb,
0xaf, 0xc2, 0x0c, 0x2c, 0xfc, 0x0a, 0xb3, 0x1b, 0x92, 0xaa, 0x14, 0x45, 0x45, 0x8f, 0x60, 0x79,
0x0b, 0x27, 0x3f, 0x78, 0x9e, 0xa5, 0x7b, 0x52, 0x76, 0x8f, 0xc2, 0xda, 0x7b, 0x14, 0xed, 0xba,
0x1b, 0xe0, 0xa3, 0xd7, 0xbd, 0x81, 0x29, 0xa3, 0xad, 0x58, 0x3f, 0xb0, 0x6c, 0x06, 0xc7, 0x06,
0xd6, 0xad, 0x8a, 0xff, 0x59, 0xe0, 0x5e, 0xd4, 0xea, 0x1a, 0xaf, 0xc0, 0x37, 0xb6, 0xf1, 0xe5,
0x60, 0xdd, 0xe0, 0xa8, 0xf3, 0x57, 0xf7, 0xf6, 0x3b, 0xd6, 0xf0, 0xa8, 0xa5, 0x33, 0xb6, 0x0e,
0xe8, 0x06, 0x87, 0x39, 0xa0, 0x1b, 0xde, 0x23, 0x3c, 0xc2, 0x4b, 0xf0, 0x3a, 0xe1, 0xf8, 0xe2,
0xe0, 0xe9, 0xec, 0xd9, 0x9e, 0x9f, 0xdd, 0xd3, 0x35, 0x44, 0x3f, 0x3d, 0xfd, 0x97, 0xcf, 0xff,
0x07, 0x00, 0x00, 0xff, 0xff, 0x79, 0x35, 0xb2, 0x7e, 0xec, 0x03, 0x00, 0x00,
}

View File

@ -0,0 +1,125 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: auth/service/proto/auth.proto
package go_micro_auth
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
import (
context "context"
client "github.com/micro/go-micro/client"
server "github.com/micro/go-micro/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ client.Option
var _ server.Option
// Client API for Auth service
type AuthService interface {
Generate(ctx context.Context, in *GenerateRequest, opts ...client.CallOption) (*GenerateResponse, error)
Validate(ctx context.Context, in *ValidateRequest, opts ...client.CallOption) (*ValidateResponse, error)
Revoke(ctx context.Context, in *RevokeRequest, opts ...client.CallOption) (*RevokeResponse, error)
}
type authService struct {
c client.Client
name string
}
func NewAuthService(name string, c client.Client) AuthService {
if c == nil {
c = client.NewClient()
}
if len(name) == 0 {
name = "go.micro.auth"
}
return &authService{
c: c,
name: name,
}
}
func (c *authService) Generate(ctx context.Context, in *GenerateRequest, opts ...client.CallOption) (*GenerateResponse, error) {
req := c.c.NewRequest(c.name, "Auth.Generate", in)
out := new(GenerateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *authService) Validate(ctx context.Context, in *ValidateRequest, opts ...client.CallOption) (*ValidateResponse, error) {
req := c.c.NewRequest(c.name, "Auth.Validate", in)
out := new(ValidateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *authService) Revoke(ctx context.Context, in *RevokeRequest, opts ...client.CallOption) (*RevokeResponse, error) {
req := c.c.NewRequest(c.name, "Auth.Revoke", in)
out := new(RevokeResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Auth service
type AuthHandler interface {
Generate(context.Context, *GenerateRequest, *GenerateResponse) error
Validate(context.Context, *ValidateRequest, *ValidateResponse) error
Revoke(context.Context, *RevokeRequest, *RevokeResponse) error
}
func RegisterAuthHandler(s server.Server, hdlr AuthHandler, opts ...server.HandlerOption) error {
type auth interface {
Generate(ctx context.Context, in *GenerateRequest, out *GenerateResponse) error
Validate(ctx context.Context, in *ValidateRequest, out *ValidateResponse) error
Revoke(ctx context.Context, in *RevokeRequest, out *RevokeResponse) error
}
type Auth struct {
auth
}
h := &authHandler{hdlr}
return s.Handle(s.NewHandler(&Auth{h}, opts...))
}
type authHandler struct {
AuthHandler
}
func (h *authHandler) Generate(ctx context.Context, in *GenerateRequest, out *GenerateResponse) error {
return h.AuthHandler.Generate(ctx, in, out)
}
func (h *authHandler) Validate(ctx context.Context, in *ValidateRequest, out *ValidateResponse) error {
return h.AuthHandler.Validate(ctx, in, out)
}
func (h *authHandler) Revoke(ctx context.Context, in *RevokeRequest, out *RevokeResponse) error {
return h.AuthHandler.Revoke(ctx, in, out)
}

View File

@ -0,0 +1,50 @@
syntax = "proto3";
package go.micro.auth;
service Auth {
rpc Generate(GenerateRequest) returns (GenerateResponse) {};
rpc Validate(ValidateRequest) returns (ValidateResponse) {};
rpc Revoke(RevokeRequest) returns (RevokeResponse) {};
}
message Account{
string id = 1;
string token = 2;
int64 created = 3;
int64 expiry = 4;
repeated Role roles = 5;
map<string, string> metadata = 6;
}
message Role {
string name = 1;
Resource resource = 2;
}
message Resource{
string name = 1;
string type = 2;
}
message GenerateRequest {
Account account = 1;
}
message GenerateResponse {
Account account = 1;
}
message ValidateRequest {
string token = 1;
}
message ValidateResponse {
Account account = 1;
}
message RevokeRequest {
string token = 1;
}
message RevokeResponse {}

128
auth/service/service.go Normal file
View File

@ -0,0 +1,128 @@
package service
import (
"context"
"time"
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/v2/auth"
pb "github.com/micro/go-micro/v2/auth/service/proto"
)
// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
svc := new(svc)
svc.Init(opts...)
return svc
}
// svc is the service implementation of the Auth interface
type svc struct {
options auth.Options
auth pb.AuthService
}
func (s *svc) String() string {
return "service"
}
func (s *svc) Init(opts ...auth.Option) error {
for _, o := range opts {
o(&s.options)
}
dc := client.DefaultClient
s.auth = pb.NewAuthService("go.micro.auth", dc)
return nil
}
// Generate a new auth account
func (s *svc) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
// construct the request
options := auth.NewGenerateOptions(opts...)
sa := &auth.Account{
Id: id,
Roles: options.Roles,
Metadata: options.Metadata,
}
req := &pb.GenerateRequest{Account: serializeAccount(sa)}
// execute the request
resp, err := s.auth.Generate(context.Background(), req)
if err != nil {
return nil, err
}
// format the response
return deserializeAccount(resp.Account), nil
}
// Revoke an authorization account
func (s *svc) Revoke(token string) error {
// contruct the request
req := &pb.RevokeRequest{Token: token}
// execute the request
_, err := s.auth.Revoke(context.Background(), req)
return err
}
// Validate an account token
func (s *svc) Validate(token string) (*auth.Account, error) {
resp, err := s.auth.Validate(context.Background(), &pb.ValidateRequest{Token: token})
if err != nil {
return nil, err
}
return deserializeAccount(resp.Account), nil
}
func serializeAccount(sa *auth.Account) *pb.Account {
roles := make([]*pb.Role, len(sa.Roles))
for i, r := range sa.Roles {
roles[i] = &pb.Role{
Name: r.Name,
}
if r.Resource != nil {
roles[i].Resource = &pb.Resource{
Name: r.Resource.Name,
Type: r.Resource.Type,
}
}
}
return &pb.Account{
Id: sa.Id,
Roles: roles,
Metadata: sa.Metadata,
}
}
func deserializeAccount(a *pb.Account) *auth.Account {
// format the response
sa := &auth.Account{
Id: a.Id,
Token: a.Token,
Created: time.Unix(a.Created, 0),
Expiry: time.Unix(a.Expiry, 0),
Metadata: a.Metadata,
}
sa.Roles = make([]*auth.Role, len(a.Roles))
for i, r := range a.Roles {
sa.Roles[i] = &auth.Role{
Name: r.Name,
}
if r.Resource != nil {
sa.Roles[i].Resource = &auth.Resource{
Name: r.Resource.Name,
Type: r.Resource.Type,
}
}
}
return sa
}

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/micro/go-micro/v2/auth"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v2/broker"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/client/selector" "github.com/micro/go-micro/v2/client/selector"
@ -55,6 +56,10 @@ import (
// tracers // tracers
// jTracer "github.com/micro/go-micro/v2/debug/trace/jaeger" // jTracer "github.com/micro/go-micro/v2/debug/trace/jaeger"
memTracer "github.com/micro/go-micro/v2/debug/trace/memory" memTracer "github.com/micro/go-micro/v2/debug/trace/memory"
// auth
jwtAuth "github.com/micro/go-micro/v2/auth/jwt"
sAuth "github.com/micro/go-micro/v2/auth/service"
) )
type Cmd interface { type Cmd interface {
@ -223,6 +228,21 @@ var (
EnvVars: []string{"MICRO_TRACER_ADDRESS"}, EnvVars: []string{"MICRO_TRACER_ADDRESS"},
Usage: "Comma-separated list of tracer addresses", Usage: "Comma-separated list of tracer addresses",
}, },
&cli.StringFlag{
Name: "auth",
EnvVars: []string{"MICRO_AUTH"},
Usage: "Auth for role based access control, e.g. service",
},
&cli.StringFlag{
Name: "auth_public_key",
EnvVars: []string{"MICRO_AUTH_PUBLIC_KEY"},
Usage: "Public key for JWT auth (base64 encoded PEM)",
},
&cli.StringFlag{
Name: "auth_private_key",
EnvVars: []string{"MICRO_AUTH_PRIVATE_KEY"},
Usage: "Private key for JWT auth (base64 encoded PEM)",
},
} }
DefaultBrokers = map[string]func(...broker.Option) broker.Broker{ DefaultBrokers = map[string]func(...broker.Option) broker.Broker{
@ -274,6 +294,11 @@ var (
// "jaeger": jTracer.NewTracer, // "jaeger": jTracer.NewTracer,
} }
DefaultAuths = map[string]func(...auth.Option) auth.Auth{
"service": sAuth.NewAuth,
"jwt": jwtAuth.NewAuth,
}
// used for default selection as the fall back // used for default selection as the fall back
defaultClient = "grpc" defaultClient = "grpc"
defaultServer = "grpc" defaultServer = "grpc"
@ -300,6 +325,7 @@ func newCmd(opts ...Option) Cmd {
Runtime: &runtime.DefaultRuntime, Runtime: &runtime.DefaultRuntime,
Store: &store.DefaultStore, Store: &store.DefaultStore,
Tracer: &trace.DefaultTracer, Tracer: &trace.DefaultTracer,
Auth: &auth.DefaultAuth,
Brokers: DefaultBrokers, Brokers: DefaultBrokers,
Clients: DefaultClients, Clients: DefaultClients,
@ -310,6 +336,7 @@ func newCmd(opts ...Option) Cmd {
Runtimes: DefaultRuntimes, Runtimes: DefaultRuntimes,
Stores: DefaultStores, Stores: DefaultStores,
Tracers: DefaultTracers, Tracers: DefaultTracers,
Auths: DefaultAuths,
} }
for _, o := range opts { for _, o := range opts {
@ -382,6 +409,16 @@ func (c *cmd) Before(ctx *cli.Context) error {
*c.opts.Tracer = r() *c.opts.Tracer = r()
} }
// Set the auth
if name := ctx.String("auth"); len(name) > 0 {
r, ok := c.opts.Auths[name]
if !ok {
return fmt.Errorf("Unsupported auth: %s", name)
}
*c.opts.Auth = r()
}
// Set the client // Set the client
if name := ctx.String("client"); len(name) > 0 { if name := ctx.String("client"); len(name) > 0 {
// only change if we have the client and type differs // only change if we have the client and type differs
@ -531,6 +568,18 @@ func (c *cmd) Before(ctx *cli.Context) error {
serverOpts = append(serverOpts, server.RegisterInterval(val*time.Second)) serverOpts = append(serverOpts, server.RegisterInterval(val*time.Second))
} }
if len(ctx.String("auth_public_key")) > 0 {
if err := (*c.opts.Auth).Init(auth.PublicKey(ctx.String("auth_public_key"))); err != nil {
log.Fatalf("Error configuring auth: %v", err)
}
}
if len(ctx.String("auth_private_key")) > 0 {
if err := (*c.opts.Auth).Init(auth.PrivateKey(ctx.String("auth_private_key"))); err != nil {
log.Fatalf("Error configuring auth: %v", err)
}
}
// client opts // client opts
if r := ctx.Int("client_retries"); r >= 0 { if r := ctx.Int("client_retries"); r >= 0 {
clientOpts = append(clientOpts, client.Retries(r)) clientOpts = append(clientOpts, client.Retries(r))

View File

@ -3,6 +3,7 @@ package cmd
import ( import (
"context" "context"
"github.com/micro/go-micro/v2/auth"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v2/broker"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/client/selector" "github.com/micro/go-micro/v2/client/selector"
@ -30,6 +31,7 @@ type Options struct {
Runtime *runtime.Runtime Runtime *runtime.Runtime
Store *store.Store Store *store.Store
Tracer *trace.Tracer Tracer *trace.Tracer
Auth *auth.Auth
Brokers map[string]func(...broker.Option) broker.Broker Brokers map[string]func(...broker.Option) broker.Broker
Clients map[string]func(...client.Option) client.Client Clients map[string]func(...client.Option) client.Client
@ -40,6 +42,7 @@ type Options struct {
Runtimes map[string]func(...runtime.Option) runtime.Runtime Runtimes map[string]func(...runtime.Option) runtime.Runtime
Stores map[string]func(...store.Option) store.Store Stores map[string]func(...store.Option) store.Store
Tracers map[string]func(...trace.Option) trace.Tracer Tracers map[string]func(...trace.Option) trace.Tracer
Auths map[string]func(...auth.Option) auth.Auth
// Other options for implementations of the interface // Other options for implementations of the interface
// can be stored in a context // can be stored in a context
@ -109,6 +112,12 @@ func Tracer(t *trace.Tracer) Option {
} }
} }
func Auth(a *auth.Auth) Option {
return func(o *Options) {
o.Auth = a
}
}
// New broker func // New broker func
func NewBroker(name string, b func(...broker.Option) broker.Broker) Option { func NewBroker(name string, b func(...broker.Option) broker.Broker) Option {
return func(o *Options) { return func(o *Options) {
@ -164,3 +173,10 @@ func NewTracer(name string, t func(...trace.Option) trace.Tracer) Option {
o.Tracers[name] = t o.Tracers[name] = t
} }
} }
// New auth func
func NewAuth(name string, t func(...auth.Option) auth.Auth) Option {
return func(o *Options) {
o.Auths[name] = t
}
}

1
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/bitly/go-simplejson v0.5.0 github.com/bitly/go-simplejson v0.5.0
github.com/bwmarrin/discordgo v0.20.2 github.com/bwmarrin/discordgo v0.20.2
github.com/coreos/etcd v3.3.18+incompatible github.com/coreos/etcd v3.3.18+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/forestgiant/sliceutil v0.0.0-20160425183142-94783f95db6c github.com/forestgiant/sliceutil v0.0.0-20160425183142-94783f95db6c
github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify v1.4.7
github.com/fsouza/go-dockerclient v1.6.0 github.com/fsouza/go-dockerclient v1.6.0

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/micro/cli/v2" "github.com/micro/cli/v2"
"github.com/micro/go-micro/v2/auth"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v2/broker"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/client/selector" "github.com/micro/go-micro/v2/client/selector"
@ -120,6 +121,13 @@ func Tracer(t trace.Tracer) Option {
} }
} }
// Auth sets the auth for the service
func Auth(a auth.Auth) Option {
return func(o *Options) {
o.Server.Init(server.Auth(a))
}
}
// Selector sets the selector for the service client // Selector sets the selector for the service client
func Selector(s selector.Selector) Option { func Selector(s selector.Selector) Option {
return func(o *Options) { return func(o *Options) {

View File

@ -5,6 +5,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/micro/go-micro/v2/auth"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v2/broker"
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v2/codec"
"github.com/micro/go-micro/v2/debug/trace" "github.com/micro/go-micro/v2/debug/trace"
@ -17,6 +18,7 @@ type Options struct {
Broker broker.Broker Broker broker.Broker
Registry registry.Registry Registry registry.Registry
Tracer trace.Tracer Tracer trace.Tracer
Auth auth.Auth
Transport transport.Transport Transport transport.Transport
Metadata map[string]string Metadata map[string]string
Name string Name string
@ -161,6 +163,13 @@ func Tracer(t trace.Tracer) Option {
} }
} }
// Auth mechanism for role based access control
func Auth(a auth.Auth) Option {
return func(o *Options) {
o.Auth = a
}
}
// Transport mechanism for communication e.g http, rabbitmq, etc // Transport mechanism for communication e.g http, rabbitmq, etc
func Transport(t transport.Transport) Option { func Transport(t transport.Transport) Option {
return func(o *Options) { return func(o *Options) {

View File

@ -68,19 +68,26 @@ func (m *memoryStore) Read(key string, opts ...store.ReadOption) ([]*store.Recor
var vals []*memoryRecord var vals []*memoryRecord
if !options.Prefix { if options.Prefix {
v, ok := m.values[key]
if !ok {
return nil, store.ErrNotFound
}
vals = []*memoryRecord{v}
} else {
for _, v := range m.values { for _, v := range m.values {
if !strings.HasPrefix(v.r.Key, key) { if !strings.HasPrefix(v.r.Key, key) {
continue continue
} }
vals = append(vals, v) vals = append(vals, v)
} }
} else if options.Suffix {
for _, v := range m.values {
if !strings.HasSuffix(v.r.Key, key) {
continue
}
vals = append(vals, v)
}
} else {
v, ok := m.values[key]
if !ok {
return nil, store.ErrNotFound
}
vals = []*memoryRecord{v}
} }
//nolint:prealloc //nolint:prealloc

View File

@ -11,6 +11,8 @@ type Options struct {
Namespace string Namespace string
// Prefix of the keys used // Prefix of the keys used
Prefix string Prefix string
// Suffix of the keys used
Suffix string
// Alternative options // Alternative options
Context context.Context Context context.Context
} }
@ -45,3 +47,10 @@ func ReadPrefix() ReadOption {
o.Prefix = true o.Prefix = true
} }
} }
// ReadSuffix uses the key as a suffix
func ReadSuffix() ReadOption {
return func(o *ReadOptions) {
o.Suffix = true
}
}

View File

@ -39,6 +39,8 @@ type Record struct {
type ReadOptions struct { type ReadOptions struct {
// Read key as a prefix // Read key as a prefix
Prefix bool Prefix bool
// Read key as a suffix
Suffix bool
} }
type ReadOption func(o *ReadOptions) type ReadOption func(o *ReadOptions)