I have a publicly exposed lambda that allows users to sign up with my site backed by AWS Cognito; or at least I'm trying to.
func (d *deps) handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
req := Request{}
err := json.Unmarshal([]byte(request.Body), &req)
if err != nil {
return events.APIGatewayProxyResponse{StatusCode: 400}, UserErrResponse
}
session := session2.Must(session2.NewSessionWithOptions(session2.Options{Profile: "default"}))
svc := cognitoidentityprovider.New(session, aws.NewConfig().WithRegion("us-east-1"))
input := &cognitoidentityprovider.SignUpInput{
Password: aws.String(req.Password),
Username: aws.String(req.Email),
ClientId: aws.String("**myclientid**"),
}
_, err = svc.SignUp(input)
if err != nil {
log.Printf("unable to sign up user with err=%v", err.Error())
return events.APIGatewayProxyResponse{StatusCode: 500}, UserErrResponse
}
return events.APIGatewayProxyResponse{StatusCode: 200}, nil
}
This lambda hangs and eventually times out on the "svc.SignUp(input)" line. No error. No feedback. No nothing.
My initial thought was that the lambda does not have permission to connect to Cognito so I gave it all permission as a first debugging measure:
resource "aws_iam_policy" "cognito_sign_in" {
name = "${local.name_prefix}LambdaCogntioPolicy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"cognito-identity:*",
"cognito-idp:*",
"cognito-sync:*",
]
Effect = "Allow",
Resource = "*"
},
]
})
}
This did not help. Still hangs. What am I missing here?
The goal is to write my own lambda's for signing users up, signing users in, and resetting passwords; all backed by Cognito.
Related
While trying to run the msgraph-sdk-go training code from here: https://github.com/microsoftgraph/msgraph-training-go, I'm getting InvalidAuthenticationTokenmsg: Access token is empty while executing the Graph API calls.
I configured a Microsoft developer account with instant sandbox for trial purpose.
I created an app registration as mentioned in the tutorial here and granted required permissions for the app.
The code is able to get the AppToken, but for calls to get Users, it fails with the above error. Am I missing something here?
I tried below code from the example for msgraph-training
func (g *GraphHelper) InitializeGraphForAppAuth() error {
clientId := os.Getenv("CLIENT_ID")
tenantId := os.Getenv("TENANT_ID")
clientSecret := os.Getenv("CLIENT_SECRET")
credential, err := azidentity.NewClientSecretCredential(tenantId, clientId, clientSecret, nil)
if err != nil {
return err
}
g.clientSecretCredential = credential
// Create an auth provider using the credential
authProvider, err := auth.NewAzureIdentityAuthenticationProviderWithScopes(g.clientSecretCredential, []string{
"https://graph.microsoft.com/.default",
})
if err != nil {
return err
}
// Create a request adapter using the auth provider
adapter, err := msgraphsdk.NewGraphRequestAdapter(authProvider)
if err != nil {
return err
}
// Create a Graph client using request adapter
client := msgraphsdk.NewGraphServiceClient(adapter)
g.appClient = client
return nil
}
// This part works, and I get the AppToken with required scope, once decoded.
func (g *GraphHelper) GetAppToken() (*string, error) {
token, err := g.clientSecretCredential.GetToken(context.Background(), policy.TokenRequestOptions{
Scopes: []string{
"https://graph.microsoft.com/.default",
},
})
if err != nil {
return nil, err
}
fmt.Println("expires on : ", token.ExpiresOn)
return &token.Token, nil
}
// The GetUsers function errors out
func (g *GraphHelper) GetUsers() (models.UserCollectionResponseable, error) {
var topValue int32 = 25
query := users.UsersRequestBuilderGetQueryParameters{
// Only request specific properties
Select: []string{"displayName", "id", "mail"},
// Get at most 25 results
Top: &topValue,
// Sort by display name
Orderby: []string{"displayName"},
}
resp, err := g.appClient.Users().
Get(context.Background(),
&users.UsersRequestBuilderGetRequestConfiguration{
QueryParameters: &query,
})
if err != nil {
fmt.Println("Users.Get got Error", err.Error(), resp)
printOdataError(err)
}
resp, err = g.appClient.Users().
Get(context.Background(),
nil)
if err != nil {
fmt.Println("Users.Get got Error with nil", err.Error(), resp)
}
return resp, err
}
I have added the User.Read.All permission in the app as mentioned in the tutorial.
Instead of getting the list of users, I'm getting below error:
Users.Get got Error error status code received from the API <nil>
error: error status code received from the API
code: InvalidAuthenticationTokenmsg: Access token is empty.Users.Get got Error with nil error status code received from the API <nil>
As you are using client Credential Flow ,you can verify your permission in azure portal , if you have User.Read.All delegated permission , Removes the delegated ones and add the corresponding application ones and don't forget to click on grant administrator consent after that. It should then work.
Hope this helps
Thanks.
Okay, so the fix that did work for me after trial and error was a version mismatch in the example and the actual application I was trying out.
The version of the beta msgraph application I was using was v0.49, whereas the msgraphsdk tutorial was using v0.48. The go mod command picked up the latest v0.49 initially I guess, I updated the go.mod file to use v0.48 after looking at the go.mod file from msgraph-training repository and things started working.
Hope this helps someone else later on.
In our Staging environment, we have credential-less access to our private S3 buckets. Access is granted to individual Docker containers. I am trying to upload a file using PutObject using the aws-sdk-go-v2 SDK library, but I'm continually getting a 403 AccessDenied api error.
My upload code looks like this:
var uploadFileFunc = func(s3Details S3Details, key string, payload []byte, params MetadataParams) (*s3.PutObjectOutput, error) {
client := getS3Client(s3Details)
return client.PutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(s3Details.Bucket),
Key: aws.String(key),
Body: bytes.NewReader(payload),
ContentType: aws.String("text/xml"),
})
}
func getS3Client(s3Details S3Details) *s3.Client {
endpointResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
if s3Details.EndpointUrl != "" {
return aws.Endpoint{
PartitionID: "aws",
URL: s3Details.EndpointUrl,
SigningRegion: s3Details.Region,
SigningMethod: s3Details.SignatureVersion,
}, nil
}
return aws.Endpoint{}, &aws.EndpointNotFoundError{}
})
cfg, _ := config.LoadDefaultConfig(context.TODO(),
config.WithEndpointDiscovery(aws.EndpointDiscoveryEnabled),
config.WithEndpointResolverWithOptions(endpointResolver))
return s3.NewFromConfig(cfg, func(o *s3.Options) {
o.Region = s3Details.Region
o.Credentials = aws.AnonymousCredentials{}
o.UsePathStyle = true
})
}
I am using aws.AnonymousCredentials{} (as our access is credential-less) but this is only to be used for unsigned requests. I cannot use NewStaticCredentialsProvider with empty values for AccessKeyID and/or SecretAccessKey as this will throw a StaticCredentialsEmptyError during the Retrieve(). Adding dummy credentials will throw an error that they are not on record. I am assuming that this is the cause of my 403 AccessDenied.
How do I sign requests without providing credentials in the Go SDK? Is it even possible? In the boto3 Python library this works fine.
First of all, I'll strongly suggest you use the v2 of the AWS SDK of Go. I'll present here how I do this so far.
First, I get the AWS config to use with this code (only relevant parts are shown):
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
Log.Fatal(err)
}
Here the package used is github.com/aws/aws-sdk-go-v2/config.
Then, I instantiate an s3Client to use for contacting AWS S3 service:
s3Client := s3.NewFromConfig(*cfg)
Here, we use this package github.com/aws/aws-sdk-go-v2/service/s3. Finally, to post your object you have to run this code:
input := &s3.PutObjectInput{
Key: aws.String("test"),
Bucket: aws.String("test"),
Body: bytes.NewReader([]byte("test")),
ACL: types.ObjectCannedACLPrivate,
}
if _, err := s3Client.PutObject(context.TODO(), input); err != nil {
return "", fmt.Errorf("fn UploadFile %w", err)
}
The new package used here is github.com/aws/aws-sdk-go-v2/service/s3/types.
This code is a simplification but you should able to achieve what you need. Furthermore, it should take very little time to update the version of the SDK and you can rely on both of them simultaneously if you've to work with a huge codebase.
Let me know if this helps!
Edit
I updated my solution by using the aws.AnonymousCredentials{} option. Now I was successfully able to upload a file into an s3 bucket with these options. Below you can find the entire solution:
package main
import (
"bytes"
"context"
"crypto/tls"
"net/http"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
)
func GetAwsConfig() (*aws.Config, error) {
cfg, err := config.LoadDefaultConfig(context.TODO(),
// config.WithClientLogMode(aws.LogRequestWithBody|aws.LogResponseWithBody),
config.WithRegion("eu-west-1"),
config.WithHTTPClient(&http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}),
config.WithEndpointResolverWithOptions(
aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
PartitionID: "aws",
URL: "http://127.0.0.1:4566",
SigningRegion: "eu-west-1",
HostnameImmutable: true,
}, nil
}),
))
if err != nil {
return nil, err
}
return &cfg, err
}
func main() {
cfg, _ := GetAwsConfig()
s3Client := s3.NewFromConfig(*cfg, func(o *s3.Options) {
o.Credentials = aws.AnonymousCredentials{}
})
if _, err := s3Client.PutObject(context.Background(), &s3.PutObjectInput{
Bucket: aws.String("mybucket"),
Key: aws.String("myfile"),
Body: bytes.NewReader([]byte("hello")),
ACL: types.ObjectCannedACLPrivate,
}); err != nil {
panic(err)
}
}
Before running the code, you've to create the bucket. I used the below command:
aws --endpoint-url=http://localhost:4566 s3 mb s3://mybucket
Thanks to this you can upload the file into the mybucket s3 bucket. To check for the file existence you can issue this command:
aws --endpoint-url=http://localhost:4566 s3 ls s3://mybucket --recursive --human-readable
Hope this helps in solving your issue!
I use an interface to interact with my entGO repository. But I have a problem: I'm using the CRUD model to design my repository, and I want to know if I need to use Transaction to create a user and then generate a token. I need to do that with Transaction to roll back in case of failure. My problem is I can only use my client at a repository level. What is the best manner to handle the transaction? Do I need to create a custom function to handle the Transaction or design my function to take transaction parameters? Here is an example of what I have:
// mariaUserRepository is data/repository implementation
// of service layer UserRepository
type mariaUserRepository struct {
Client *ent.Client
}
// NewUserRepository is a factory for initializing User Repositories
func NewUserRepository(client *ent.Client) models.UserRepository {
return &mariaUserRepository{
Client: client,
}
}
// Create reaches out to database entGO api
func (r *mariaUserRepository) Create(ctx context.Context, u *ent.User) error {
// check if a user already exist
check, err := r.Client.User.
Query().
Where(user.Email(u.Email)).Exist(ctx)
if err != nil {
utils.Logger.Info("Error when checking user",
zap.String("email", u.Email),
zap.Error(err),
)
return handle_errors.NewInternal()
}
if check {
err := handle_errors.NewConflict("email", u.Email)
utils.Logger.Info("Error when Register User",
zap.String("email", u.Email),
zap.Error(err),
)
return err
}
if u, err = r.Client.User.
Create().
SetFirstName(u.FirstName).
SetLastName(u.LastName).
SetEmail(u.Email).
SetGender(u.Gender).
SetUserType(u.UserType).
SetPasswordHash(u.PasswordHash).
Save(ctx); err != nil {
utils.Logger.Info("Error when create user",
zap.Any("User", u),
zap.Error(err),
)
return handle_errors.NewInternal()
}
return nil
}
My problem come from the comment that say to implement rollback in case of failure.
err := h.UserService.Signup(ctx, u)
if err != nil {
log.Printf("Failed to sign up user: %v\n", err.Error())
c.JSON(apperrors.Status(err), gin.H{
"error": err,
})
return
}
// create token pair as strings
tokens, err := h.TokenService.NewPairFromUser(ctx, u, "")
if err != nil {
log.Printf("Failed to create tokens for user: %v\n", err.Error())
// may eventually implement rollback logic here
// meaning, if we fail to create tokens after creating a user,
// we make sure to clear/delete the created user in the database
c.JSON(apperrors.Status(err), gin.H{
"error": err,
})
return
}
```
Using Go and AWS-SDK
I'm attempting to query route53 CNAME and A records as listed in the AWS Console under Route53 -> Hosted Zones. I'm able to query using the following code, but it requires the (cryptic) HostedZoneId I have to know ahead of time.
Is there a different function, or a HostedZoneId lookup based on the Domain Name such as XXX.XXX.com ?
AWSLogin(instance)
svc := route53.New(instance.AWSSession)
listParams := &route53.ListResourceRecordSetsInput{
HostedZoneId: aws.String("Z2798GPJN9CUFJ"), // Required
// StartRecordType: aws.String("CNAME"),
}
respList, err := svc.ListResourceRecordSets(listParams)
if err != nil {
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println("All records:")
fmt.Println(respList)
edit: oh, additionally, the StartRecordType with the value "CNAME" throws a validation error, so I'm not sure what I should be using there.
You first have to do a lookup to get the HostedZoneID. Here is the func I wrote for it. :
func GetHostedZoneIdByNameLookup(awsSession string, HostedZoneName string) (HostedZoneID string, err error) {
svc := route53.New(awsSession)
listParams := &route53.ListHostedZonesByNameInput{
DNSName: aws.String(HostedZoneName), // Required
}
req, resp := svc.ListHostedZonesByNameRequest(listParams)
err = req.Send()
if err != nil {
return "", err
}
HostedZoneID = *resp.HostedZones[0].Id
// remove the /hostedzone/ path if it's there
if strings.HasPrefix(HostedZoneID, "/hostedzone/") {
HostedZoneID = strings.TrimPrefix(HostedZoneID, "/hostedzone/")
}
return HostedZoneID, nil
}
I'm working with framework GIN and Gin-JWT in Golang.
So far so good, I was able to authorize and authenticate my REST API with JWT following the example in Gin-JWT package.
I'm trying now to implement some kind of Role in my API.
The flow would be:
Login and auth
Create the JWT with inside the userID and the RoleID
When I call a REST API I confront the role associated to the API with the RoleID in JWT to authorized
So far I have this in my main:
jwtAfp := InitJwtMiddleware(db)
afb := r.Group("api/v1/afb")
afb.Use(jwtAfp.MiddlewareFunc())
afb.GET("/ping", afbController.Ping)
and this for the InitJwtMiddleware using Gin-JWT
func InitJwtMiddleware(db *gorm.DB) *jwt.GinJWTMiddleware {
return &jwt.GinJWTMiddleware{
Realm: "afb",
Key: []byte("secret pwd"),
Timeout: time.Hour,
MaxRefresh: time.Hour,
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(*model.User); ok {
return jwt.MapClaims{
"afb": v.ID,
}
}
return jwt.MapClaims{}
},
Authenticator: func(c *gin.Context) (interface{}, error) {
var loginVals login
if err := c.Bind(&loginVals); err != nil {
return "", jwt.ErrMissingLoginValues
}
email := loginVals.Username
password := loginVals.Password
var u model.User
db.Where("email = ?", email).First(&u)
if service.CheckPasswordHash(password, u.Password) {
return &u, nil
}
return nil, jwt.ErrFailedAuthentication
},
Authorizator: func(data interface{}, c *gin.Context) bool {
claims := jwt.ExtractClaims(c)
v, ok := data.(float64)
if ok && v == claims["afb"] {
return true
}
return false
},
Unauthorized: func(c *gin.Context, code int, message string) {
c.JSON(code, gin.H{
"code": code,
"message": message,
})
},
TokenHeadName: "Bearer",
TimeFunc: time.Now,
}
}
I would like to add the checking on the Role in the Authorizator section but I'm struggling on how i can do this.
I come up with passing in the InitJwtMiddleware(db) function also the role, this will work but I don't like the idea to "instaziate" a GinJWTMiddleware for each ROLE/API. Or if I could know inside the middleware which function (controller) will be called later I can then figure out if authorize or not. But even this solutin sound awkward to me. I think there will be a most elegant solution, any ideas?
You can try this:
https://github.com/kyfk/gin-jwt
It's the simplest auth[orization/entication] library.
The VerifyPerm function could be helpful for role management.
There's a complete example
func main() {
auth, err := jwt.New(jwt.Auth{
SecretKey: []byte("must change here"),
// Authenticator authenticates a request and return jwt.MapClaims
// that contains a user information of the request.
Authenticator: func(c *gin.Context) (jwt.MapClaims, error) {
var loginForm LoginForm
if err := c.ShouldBind(&loginForm); err != nil {
return nil, jwt.ErrorAuthenticationFailed
}
u, ok := authenticate(req.Username, req.Password)
if ok {
return nil, jwt.ErrorAuthenticationFailed
}
return jwt.MapClaims{
"username": u.Username,
"role": u.Role,
}, nil
},
// UserFetcher takes a jwt.MapClaims and return a user object.
UserFetcher: func(c *gin.Context, claims jwt.MapClaims) (interface{}, error) {
username, ok := claims["username"].(string)
if !ok {
return nil, nil
}
return findByUsername(username)
},
})
// some lines
e.Use(jwt.ErrorHandler)
// issue authorization token
e.POST("/login", auth.AuthenticateHandler)
// refresh token expiration
e.POST("/auth/refresh_token", auth.RefreshHandler)
// role management
e.GET("/operator/hello", Operator(auth), SayHello) // this is only for Operator
e.GET("/admin/hello", Admin(auth), SayHello) // this is only for Admin
}
func Operator(m jwt.Auth) gin.HandlerFunc {
return m.VerifyPerm(func(claims jwt.MapClaims) bool {
return role(claims).IsOperator()
})
}
func Admin(m jwt.Auth) gin.HandlerFunc {
return m.VerifyPerm(func(claims jwt.MapClaims) bool {
return role(claims).IsAdmin()
})
}