├── .gitignore ├── README.md ├── aws-ec2 ├── .gitignore ├── go.mod ├── go.sum └── main.go ├── aws-s3-testing ├── go.mod ├── go.sum ├── main.go ├── main_test.go └── testdata │ └── test.txt ├── aws-s3 ├── go.mod ├── go.sum └── main.go ├── azure-instance ├── .gitignore ├── go.mod ├── go.sum └── main.go ├── dns-demo ├── cmd │ └── dns-resolver │ │ └── main.go ├── go.mod ├── go.sum └── pkg │ └── dns │ ├── resolver.go │ └── resolver_test.go ├── dns-start ├── cmd │ └── dns-resolver │ │ └── main.go ├── go.mod ├── go.sum └── pkg │ └── dns │ ├── resolver.go │ └── resolver_test.go ├── go.work ├── go.work.sum ├── hello-world-arguments ├── go.mod └── main.go ├── hello-world ├── go.mod └── main.go ├── http-get-errorhandling ├── error.go ├── go.mod └── main.go ├── http-get-flags ├── error.go ├── go.mod └── main.go ├── http-get-functions ├── go.mod └── main.go ├── http-get-json-map ├── go.mod └── main.go ├── http-get-json ├── go.mod └── main.go ├── http-get ├── go.mod └── main.go ├── http-login-packaged ├── cmd │ └── http-login │ │ └── main.go ├── go.mod └── pkg │ └── api │ ├── error.go │ ├── get.go │ ├── init.go │ ├── login.go │ └── transport.go ├── http-login-tests ├── cmd │ └── http-login │ │ └── main.go ├── go.mod └── pkg │ └── api │ ├── error.go │ ├── get.go │ ├── get_test.go │ ├── init.go │ ├── login.go │ ├── transport.go │ └── transport_test.go ├── http-login ├── error.go ├── go.mod ├── login.go ├── main.go └── transport.go ├── kubernetes-deploy-github ├── .dockerignore ├── Dockerfile ├── README.md ├── deploy.yaml ├── go.mod ├── go.sum ├── main.go ├── server.go └── server_test.go ├── kubernetes-deploy ├── app.yaml ├── go.mod ├── go.sum └── main.go ├── mutex-demo ├── go.mod └── main.go ├── oidc-demo ├── .gitignore ├── Dockerfile ├── cmd │ ├── appserver │ │ ├── jwt.go │ │ ├── jwt_test.go │ │ └── main.go │ └── server │ │ └── main.go ├── config.yaml ├── go.mod ├── go.sum └── pkg │ ├── oidc │ ├── discovery.go │ ├── rand.go │ └── types.go │ ├── server │ ├── authorization.go │ ├── authorization_test.go │ ├── config.go │ ├── discovery.go │ ├── http.go │ ├── http_test.go │ ├── jwks.go │ ├── login.go │ ├── login_test.go │ ├── templates │ │ └── login.html │ ├── token.go │ ├── token_test.go │ ├── types.go │ ├── userinfo.go │ └── userinfo_test.go │ └── users │ └── auth.go ├── oidc-start ├── .gitignore ├── cmd │ ├── appserver │ │ ├── jwt.go │ │ ├── jwt_test.go │ │ └── main.go │ └── server │ │ └── main.go ├── go.mod ├── go.sum └── pkg │ ├── oidc │ ├── discovery.go │ ├── rand.go │ └── types.go │ ├── server │ ├── authorization.go │ ├── authorization_test.go │ ├── config.go │ ├── config_test.go │ ├── discovery.go │ ├── http.go │ ├── http_test.go │ ├── jwks.go │ ├── login.go │ ├── login_test.go │ ├── templates │ │ └── login.html │ ├── token.go │ ├── token_test.go │ ├── types.go │ ├── userinfo.go │ └── userinfo_test.go │ └── users │ └── auth.go ├── reader-example ├── go.mod └── main.go ├── slices-demo ├── cmd │ └── array-and-slice │ │ └── main.go └── go.mod ├── ssh-demo ├── .gitignore ├── cmd │ ├── client │ │ └── main.go │ ├── keygen │ │ └── main.go │ └── server │ │ └── main.go ├── go.mod ├── go.sum ├── keygen.go └── server.go ├── test-server ├── Dockerfile ├── Dockerfile.scratch ├── README.md ├── assignment1.go ├── go.mod ├── go.sum ├── main.go ├── ratelimit.go └── start-test-server.sh ├── tls-demo ├── .gitignore ├── cmd │ ├── letsencrypt-server │ │ └── main.go │ ├── mtls-client │ │ └── main.go │ ├── mtls-server │ │ └── main.go │ ├── test-server │ │ └── main.go │ └── tls │ │ └── main.go ├── go.mod ├── go.sum ├── pkg │ ├── cert │ │ ├── pem.go │ │ ├── types.go │ │ └── x509.go │ ├── cmd │ │ ├── ca.go │ │ ├── cert.go │ │ ├── create.go │ │ ├── key.go │ │ └── root.go │ └── key │ │ └── rsa.go └── tls.yaml ├── tls-start ├── .gitignore ├── cmd │ ├── test-server │ │ └── main.go │ └── tls │ │ └── main.go ├── go.mod ├── go.sum ├── pkg │ ├── cert │ │ ├── pem.go │ │ ├── types.go │ │ └── x509.go │ ├── cmd │ │ └── root.go │ └── key │ │ └── rsa.go └── tls.yaml └── types-demo ├── cmd ├── generics │ └── main.go ├── json-parsing │ └── main.go └── type-switch │ └── main.go └── go.mod /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # golang-for-devops-course 2 | 3 | Course material for [Golang For DevOps And Cloud Engineers](https://www.udemy.com/course/golang-for-devops-and-cloud-engineers/?referralCode=5A05F011338E0C54EAE7) 4 | -------------------------------------------------------------------------------- /aws-ec2/.gitignore: -------------------------------------------------------------------------------- 1 | go-aws-ec2.pem 2 | -------------------------------------------------------------------------------- /aws-ec2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/aws-ec2 2 | 3 | go 1.18 4 | 5 | require github.com/aws/aws-sdk-go-v2/config v1.15.8 6 | 7 | require ( 8 | github.com/aws/aws-sdk-go-v2 v1.16.4 // indirect 9 | github.com/aws/aws-sdk-go-v2/credentials v1.12.3 // indirect 10 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.5 // indirect 11 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.11 // indirect 12 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.5 // indirect 13 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12 // indirect 14 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.44.0 // indirect 15 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.5 // indirect 16 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.6 // indirect 17 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.6 // indirect 18 | github.com/aws/smithy-go v1.11.2 // indirect 19 | github.com/jmespath/go-jmespath v0.4.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /aws-ec2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go-v2 v1.16.4 h1:swQTEQUyJF/UkEA94/Ga55miiKFoXmm/Zd67XHgmjSg= 2 | github.com/aws/aws-sdk-go-v2 v1.16.4/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= 3 | github.com/aws/aws-sdk-go-v2/config v1.15.8 h1:Mk9aPT1JiPkhZO9PIP1w2ramuRw95d9w5YNOM3poTKk= 4 | github.com/aws/aws-sdk-go-v2/config v1.15.8/go.mod h1:Z/guryqWzLw1T3pJbFA0/V3aVXw0sX5oH4lXXiD67aY= 5 | github.com/aws/aws-sdk-go-v2/credentials v1.12.3 h1:1kPx2lGjvopx7IMqKFmqmhqcuDZQ7pvq9xNXPP5c6qo= 6 | github.com/aws/aws-sdk-go-v2/credentials v1.12.3/go.mod h1:p6/NGiaGKKM3ihOt/W08Ikz7/F95WhvgjA4x6MWKdS8= 7 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.5 h1:YPxclBeE07HsLQE8vtjC8T2emcTjM9nzqsnDi2fv5UM= 8 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.5/go.mod h1:WAPnuhG5IQ/i6DETFl5NmX3kKqCzw7aau9NHAGcm4QE= 9 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.11 h1:gsqHplNh1DaQunEKZISK56wlpbCg0yKxNVvGWCFuF1k= 10 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.11/go.mod h1:tmUB6jakq5DFNcXsXOA/ZQ7/C8VnSKYkx58OI7Fh79g= 11 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.5 h1:PLFj+M2PgIDHG//hw3T0O0KLI4itVtAjtxrZx4AHPLg= 12 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.5/go.mod h1:fV1AaS2gFc1tM0RCb015FJ0pvWVUfJZANzjwoO4YakM= 13 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12 h1:j0VqrjtgsY1Bx27tD0ysay36/K4kFMWRp9K3ieO9nLU= 14 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12/go.mod h1:00c7+ALdPh4YeEUPXJzyU0Yy01nPGOq2+9rUaz05z9g= 15 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.44.0 h1:ZuDiiY51GwGqYZ3NVMhRxqyolRy2JN4RGE1ERZ+GdyU= 16 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.44.0/go.mod h1:b2SVOmsP7A9VlTpfkJAVbU3d+TQfD76x9IUNbvynAbM= 17 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.5 h1:gRW1ZisKc93EWEORNJRvy/ZydF3o6xLSveJHdi1Oa0U= 18 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.5/go.mod h1:ZbkttHXaVn3bBo/wpJbQGiiIWR90eTBUVBrEHUEQlho= 19 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.6 h1:AnTIdD439WgYNyVldYlpccGWY2EIXoUNmVzTDbFqCsg= 20 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.6/go.mod h1:TFVe6Rr2joVLsYQ1ABACXgOC6lXip/qpX2x5jWg/A9w= 21 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.6 h1:aYToU0/iazkMY67/BYLt3r6/LT/mUtarLAF5mGof1Kg= 22 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.6/go.mod h1:rP1rEOKAGZoXp4iGDxSXFvODAtXpm34Egf0lL0eshaQ= 23 | github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= 24 | github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= 25 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 27 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 28 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 29 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 32 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 33 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 34 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 35 | -------------------------------------------------------------------------------- /aws-ec2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/aws/aws-sdk-go-v2/aws" 10 | "github.com/aws/aws-sdk-go-v2/config" 11 | "github.com/aws/aws-sdk-go-v2/service/ec2" 12 | "github.com/aws/aws-sdk-go-v2/service/ec2/types" 13 | ) 14 | 15 | func main() { 16 | var ( 17 | instanceId string 18 | err error 19 | ) 20 | if instanceId, err = createEC2(context.Background(), "us-east-1"); err != nil { 21 | fmt.Printf("Error: %s", err) 22 | os.Exit(1) 23 | } 24 | 25 | fmt.Printf("instance id: %s\n", instanceId) 26 | } 27 | 28 | func createEC2(ctx context.Context, region string) (string, error) { 29 | cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region)) 30 | if err != nil { 31 | return "", fmt.Errorf("LoadDefaultConfig error: %s", err) 32 | } 33 | 34 | ec2Client := ec2.NewFromConfig(cfg) 35 | 36 | existingKeyPairs, err := ec2Client.DescribeKeyPairs(ctx, &ec2.DescribeKeyPairsInput{ 37 | KeyNames: []string{"go-aws-ec2"}, 38 | // or: 39 | //Filters: []types.Filter{ 40 | // { 41 | // Name: aws.String("key-name"), 42 | // Values: []string{"go-aws-ec2"}, 43 | // }, 44 | //}, 45 | }) 46 | if err != nil && !strings.Contains(err.Error(), "InvalidKeyPair.NotFound") { 47 | return "", fmt.Errorf("DescribeKeyPairs error: %s", err) 48 | } 49 | 50 | if existingKeyPairs == nil || len(existingKeyPairs.KeyPairs) == 0 { 51 | keyPair, err := ec2Client.CreateKeyPair(ctx, &ec2.CreateKeyPairInput{ 52 | KeyName: aws.String("go-aws-ec2"), 53 | }) 54 | if err != nil { 55 | return "", fmt.Errorf("CreateKeyPair error: %s", err) 56 | } 57 | 58 | err = os.WriteFile("go-aws-ec2.pem", []byte(*keyPair.KeyMaterial), 0600) 59 | if err != nil { 60 | return "", fmt.Errorf("WriteFile (keypair) error: %s", err) 61 | } 62 | } 63 | 64 | describeImages, err := ec2Client.DescribeImages(ctx, &ec2.DescribeImagesInput{ 65 | Filters: []types.Filter{ 66 | { 67 | Name: aws.String("name"), 68 | Values: []string{"ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"}, 69 | }, 70 | { 71 | Name: aws.String("virtualization-type"), 72 | Values: []string{"hvm"}, 73 | }, 74 | }, 75 | Owners: []string{"099720109477"}, // see https://ubuntu.com/server/docs/cloud-images/amazon-ec2 76 | }) 77 | if err != nil { 78 | return "", fmt.Errorf("DescribeImages error: %s", err) 79 | } 80 | if len(describeImages.Images) == 0 { 81 | return "", fmt.Errorf("describeImages has empty length (%d)", len(describeImages.Images)) 82 | } 83 | runInstance, err := ec2Client.RunInstances(ctx, &ec2.RunInstancesInput{ 84 | ImageId: describeImages.Images[0].ImageId, 85 | InstanceType: types.InstanceTypeT3Micro, 86 | KeyName: aws.String("go-aws-ec2"), 87 | MinCount: aws.Int32(1), 88 | MaxCount: aws.Int32(1), 89 | }) 90 | if err != nil { 91 | return "", fmt.Errorf("RunInstance error: %s", err) 92 | } 93 | 94 | if len(runInstance.Instances) == 0 { 95 | return "", fmt.Errorf("RunInstance has empty length (%d)", len(runInstance.Instances)) 96 | } 97 | 98 | return *runInstance.Instances[0].InstanceId, nil 99 | } 100 | -------------------------------------------------------------------------------- /aws-s3-testing/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/aws-s3-testing 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go-v2 v1.16.4 7 | github.com/aws/aws-sdk-go-v2/config v1.15.8 8 | github.com/aws/aws-sdk-go-v2/service/s3 v1.26.10 9 | ) 10 | 11 | require github.com/jmespath/go-jmespath v0.4.0 // indirect 12 | 13 | require ( 14 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect 15 | github.com/aws/aws-sdk-go-v2/credentials v1.12.3 // indirect 16 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.5 // indirect 17 | github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.13 18 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.11 // indirect 19 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.5 // indirect 20 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12 // indirect 21 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.2 // indirect 22 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect 23 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.6 // indirect 24 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.5 // indirect 25 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.5 // indirect 26 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.6 // indirect 27 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.6 // indirect 28 | github.com/aws/smithy-go v1.11.2 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /aws-s3-testing/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | 11 | "github.com/aws/aws-sdk-go-v2/aws" 12 | "github.com/aws/aws-sdk-go-v2/config" 13 | "github.com/aws/aws-sdk-go-v2/feature/s3/manager" 14 | "github.com/aws/aws-sdk-go-v2/service/s3" 15 | "github.com/aws/aws-sdk-go-v2/service/s3/types" 16 | ) 17 | 18 | const bucketName = "aws-demo-test-bucket-95fd1" 19 | const regionName = "us-east-1" 20 | 21 | type S3Client interface { 22 | ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error) 23 | CreateBucket(ctx context.Context, params *s3.CreateBucketInput, optFns ...func(*s3.Options)) (*s3.CreateBucketOutput, error) 24 | } 25 | 26 | type S3Uploader interface { 27 | Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*manager.Uploader)) (*manager.UploadOutput, error) 28 | } 29 | type S3Downloader interface { 30 | Download(ctx context.Context, w io.WriterAt, input *s3.GetObjectInput, options ...func(*manager.Downloader)) (n int64, err error) 31 | } 32 | 33 | func main() { 34 | var ( 35 | s3Client *s3.Client 36 | err error 37 | out []byte 38 | ) 39 | ctx := context.Background() 40 | if s3Client, err = initS3Client(ctx); err != nil { 41 | fmt.Printf("initS3Client error: %s", err) 42 | os.Exit(1) 43 | } 44 | if err = createS3Bucket(ctx, s3Client); err != nil { 45 | fmt.Printf("initS3Client error: %s", err) 46 | os.Exit(1) 47 | } 48 | if err = uploadToS3Bucket(ctx, manager.NewUploader(s3Client), "test.txt"); err != nil { 49 | fmt.Printf("uploadToS3Bucket error: %s", err) 50 | os.Exit(1) 51 | } 52 | fmt.Printf("Upload complete.\n") 53 | if out, err = downloadFromS3(ctx, manager.NewDownloader(s3Client)); err != nil { 54 | fmt.Printf("uploadToS3Bucket error: %s", err) 55 | os.Exit(1) 56 | } 57 | fmt.Printf("Download complete: %s\n", out) 58 | } 59 | 60 | func initS3Client(ctx context.Context) (*s3.Client, error) { 61 | cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(regionName)) 62 | if err != nil { 63 | return nil, fmt.Errorf("unable to load SDK config, %s", err) 64 | } 65 | 66 | return s3.NewFromConfig(cfg), nil 67 | } 68 | 69 | func createS3Bucket(ctx context.Context, s3Client S3Client) error { 70 | allBuckets, err := s3Client.ListBuckets(ctx, &s3.ListBucketsInput{}) 71 | if err != nil { 72 | return fmt.Errorf("ListBuckets error: %s", err) 73 | } 74 | 75 | found := false 76 | for _, bucket := range allBuckets.Buckets { 77 | if *bucket.Name == bucketName { 78 | found = true 79 | } 80 | } 81 | if !found { 82 | _, err := s3Client.CreateBucket(ctx, &s3.CreateBucketInput{ 83 | Bucket: aws.String(bucketName), 84 | CreateBucketConfiguration: &types.CreateBucketConfiguration{ 85 | LocationConstraint: regionName, 86 | }, 87 | }) 88 | if err != nil { 89 | return fmt.Errorf("CreateBucket error: %s", err) 90 | } 91 | } 92 | 93 | return nil 94 | } 95 | 96 | func uploadToS3Bucket(ctx context.Context, uploader S3Uploader, filename string) error { 97 | testFile, err := ioutil.ReadFile(filename) 98 | if err != nil { 99 | return fmt.Errorf("ReadFile error: %s", err) 100 | } 101 | 102 | _, err = uploader.Upload(ctx, &s3.PutObjectInput{ 103 | Bucket: aws.String(bucketName), 104 | Key: aws.String(filename), 105 | Body: bytes.NewReader(testFile), 106 | }) 107 | if err != nil { 108 | return fmt.Errorf("Upload error: %s", err) 109 | } 110 | return nil 111 | } 112 | 113 | func downloadFromS3(ctx context.Context, downloader S3Downloader) ([]byte, error) { 114 | buffer := manager.NewWriteAtBuffer([]byte{}) 115 | 116 | numBytes, err := downloader.Download(ctx, buffer, &s3.GetObjectInput{ 117 | Bucket: aws.String(bucketName), 118 | Key: aws.String("test.txt"), 119 | }) 120 | if err != nil { 121 | return nil, fmt.Errorf("Download error: %s", err) 122 | } 123 | 124 | if numBytesReceived := len(buffer.Bytes()); numBytes != int64(numBytesReceived) { 125 | return nil, fmt.Errorf("Numbytes received doesn't match: %d vs %d", numBytes, numBytesReceived) 126 | } 127 | 128 | return buffer.Bytes(), nil 129 | } 130 | -------------------------------------------------------------------------------- /aws-s3-testing/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/feature/s3/manager" 9 | "github.com/aws/aws-sdk-go-v2/service/s3" 10 | "github.com/aws/aws-sdk-go-v2/service/s3/types" 11 | ) 12 | 13 | type MockS3Client struct { 14 | ListBucketsOutput *s3.ListBucketsOutput 15 | CreateBucketOutput *s3.CreateBucketOutput 16 | } 17 | 18 | func (m MockS3Client) ListBuckets(ctx context.Context, params *s3.ListBucketsInput, optFns ...func(*s3.Options)) (*s3.ListBucketsOutput, error) { 19 | return m.ListBucketsOutput, nil 20 | } 21 | func (m MockS3Client) CreateBucket(ctx context.Context, params *s3.CreateBucketInput, optFns ...func(*s3.Options)) (*s3.CreateBucketOutput, error) { 22 | return m.CreateBucketOutput, nil 23 | } 24 | 25 | type MockS3Uploader struct { 26 | UploadOutput *manager.UploadOutput 27 | } 28 | 29 | func (m MockS3Uploader) Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*manager.Uploader)) (*manager.UploadOutput, error) { 30 | return m.UploadOutput, nil 31 | } 32 | 33 | func TestCreateS3Bucket(t *testing.T) { 34 | ctx := context.Background() 35 | err := createS3Bucket(ctx, MockS3Client{ 36 | ListBucketsOutput: &s3.ListBucketsOutput{ 37 | Buckets: []types.Bucket{ 38 | { 39 | Name: aws.String("test-bucket"), 40 | }, 41 | { 42 | Name: aws.String("test-bucket-2"), 43 | }, 44 | }, 45 | }, 46 | CreateBucketOutput: &s3.CreateBucketOutput{}, 47 | }) 48 | if err != nil { 49 | t.Fatalf("createS3Bucket error: %s", err) 50 | } 51 | } 52 | 53 | func TestUploadToS3Bucket(t *testing.T) { 54 | mockUploader := MockS3Uploader{ 55 | UploadOutput: &manager.UploadOutput{}, 56 | } 57 | err := uploadToS3Bucket(context.Background(), mockUploader, "testdata/test.txt") 58 | if err != nil { 59 | t.Fatalf("UploadToS3Bucket error: %s", err) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /aws-s3-testing/testdata/test.txt: -------------------------------------------------------------------------------- 1 | this is a test file -------------------------------------------------------------------------------- /aws-s3/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/aws-s3 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go-v2 v1.16.4 7 | github.com/aws/aws-sdk-go-v2/config v1.15.8 8 | github.com/aws/aws-sdk-go-v2/service/s3 v1.26.10 9 | ) 10 | 11 | require github.com/jmespath/go-jmespath v0.4.0 // indirect 12 | 13 | require ( 14 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect 15 | github.com/aws/aws-sdk-go-v2/credentials v1.12.3 // indirect 16 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.5 // indirect 17 | github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.13 18 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.11 // indirect 19 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.5 // indirect 20 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.12 // indirect 21 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.2 // indirect 22 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect 23 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.6 // indirect 24 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.5 // indirect 25 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.5 // indirect 26 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.6 // indirect 27 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.6 // indirect 28 | github.com/aws/smithy-go v1.11.2 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /aws-s3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/aws/aws-sdk-go-v2/aws" 10 | "github.com/aws/aws-sdk-go-v2/config" 11 | "github.com/aws/aws-sdk-go-v2/feature/s3/manager" 12 | "github.com/aws/aws-sdk-go-v2/service/s3" 13 | ) 14 | 15 | const s3BucketName = "go-aws-test-xz9" 16 | 17 | func main() { 18 | var ( 19 | s3Client *s3.Client 20 | err error 21 | out []byte 22 | ) 23 | if s3Client, err = initS3Client(context.Background(), "us-east-1"); err != nil { 24 | fmt.Printf("initConfig error: %s", err) 25 | os.Exit(1) 26 | } 27 | if err = createS3Bucket(context.Background(), s3Client); err != nil { 28 | fmt.Printf("createS3Bucket error: %s", err) 29 | os.Exit(1) 30 | } 31 | if err = uploadFileToS3(context.Background(), s3Client); err != nil { 32 | fmt.Printf("uploadFileToS3 error: %s", err) 33 | os.Exit(1) 34 | } 35 | fmt.Printf("Uploaded file.\n") 36 | if out, err = downloadFileFromS3(context.Background(), s3Client); err != nil { 37 | fmt.Printf("uploadFileToS3 error: %s", err) 38 | os.Exit(1) 39 | } 40 | fmt.Printf("Downloaded file with contents: %s", out) 41 | } 42 | 43 | func initS3Client(ctx context.Context, region string) (*s3.Client, error) { 44 | cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region)) 45 | if err != nil { 46 | return nil, fmt.Errorf("Config error: %s", err) 47 | } 48 | return s3.NewFromConfig(cfg), nil 49 | } 50 | 51 | func createS3Bucket(ctx context.Context, s3Client *s3.Client) error { 52 | 53 | _, err := s3Client.CreateBucket(ctx, &s3.CreateBucketInput{ 54 | Bucket: aws.String(s3BucketName), 55 | }) 56 | if err != nil { 57 | return fmt.Errorf("CreateBucket error: %s", err) 58 | } 59 | return nil 60 | } 61 | 62 | func uploadFileToS3(ctx context.Context, s3Client *s3.Client) error { 63 | uploader := manager.NewUploader(s3Client) 64 | _, err := uploader.Upload(ctx, &s3.PutObjectInput{ 65 | Bucket: aws.String(s3BucketName), 66 | Key: aws.String("test.txt"), 67 | Body: strings.NewReader("this is a test"), 68 | }) 69 | if err != nil { 70 | return fmt.Errorf("Upload error: %s", err) 71 | } 72 | return nil 73 | } 74 | func downloadFileFromS3(ctx context.Context, s3Client *s3.Client) ([]byte, error) { 75 | buffer := manager.NewWriteAtBuffer([]byte{}) 76 | 77 | downloader := manager.NewDownloader(s3Client) 78 | numBytes, err := downloader.Download(ctx, buffer, &s3.GetObjectInput{ 79 | Bucket: aws.String(s3BucketName), 80 | Key: aws.String("test.txt"), 81 | }) 82 | if err != nil { 83 | return buffer.Bytes(), fmt.Errorf("Upload error: %s", err) 84 | } 85 | 86 | if bytesReceived := int64(len(buffer.Bytes())); numBytes != bytesReceived { 87 | return buffer.Bytes(), fmt.Errorf("Incorrect number of bytes returned. Got %d, but expected %d", numBytes, bytesReceived) 88 | } 89 | return buffer.Bytes(), nil 90 | } 91 | -------------------------------------------------------------------------------- /azure-instance/.gitignore: -------------------------------------------------------------------------------- 1 | mykey.* 2 | -------------------------------------------------------------------------------- /azure-instance/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/azure-instance 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 7 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 8 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 9 | ) 10 | 11 | require ( 12 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect 13 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 // indirect 14 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 // indirect 15 | github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect 16 | github.com/golang-jwt/jwt v3.2.1+incompatible // indirect 17 | github.com/golang-jwt/jwt/v4 v4.4.1 // indirect 18 | github.com/google/uuid v1.1.1 // indirect 19 | github.com/kylelemons/godebug v1.1.0 // indirect 20 | github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect 21 | github.com/wardviaene/golang-for-devops-course/ssh-demo v0.0.0-20220613154127-4d821abdce2f // indirect 22 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect 23 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect 24 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect 25 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect 26 | golang.org/x/text v0.3.7 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /azure-instance/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0 h1:sVPhtT2qjO86rTUaWMr4WoES4TkjGnzcioXcnHV9s5k= 2 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= 3 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 h1:Ut0ZGdOwJDw0npYEg+TLlPls3Pq6JiZaP2/aGKir7Zw= 4 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= 5 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= 6 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= 7 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY= 8 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= 9 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 h1:/Di3vB4sNeQ+7A8efjUVENvyB945Wruvstucqp7ZArg= 10 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0/go.mod h1:gM3K25LQlsET3QR+4V74zxCsFAy0r6xMNN9n80SZn+4= 11 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 h1:nBy98uKOIfun5z6wx6jwWLrULcM0+cjBalBFZlEZ7CA= 12 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0/go.mod h1:243D9iHbcQXoFUtgHJwL7gl2zx1aDuDMjvBZVGr2uW0= 13 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= 14 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s= 15 | github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE= 16 | github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= 17 | github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= 18 | github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 19 | github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= 20 | github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 21 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 22 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 23 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 24 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 25 | github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= 26 | github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= 27 | github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= 28 | github.com/wardviaene/golang-for-devops-course/ssh-demo v0.0.0-20220613154127-4d821abdce2f h1:04DZtFhIucQxargJpUClZvIQ1FNSJHCOnbOhBOVTzos= 29 | github.com/wardviaene/golang-for-devops-course/ssh-demo v0.0.0-20220613154127-4d821abdce2f/go.mod h1:nMRrSdJ6buN2/nVCX4zAr1VTIzeZNqK7SgTepO1zRdA= 30 | golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 h1:Tgea0cVUD0ivh5ADBX4WwuI12DUd2to3nCYe2eayMIw= 31 | golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 32 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= 33 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 34 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= 35 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 36 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 37 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 38 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= 40 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 41 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 42 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 43 | -------------------------------------------------------------------------------- /dns-demo/cmd/dns-resolver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | 8 | "github.com/wardviaene/golang-for-devops-course/dns-demo/pkg/dns" 9 | ) 10 | 11 | func main() { 12 | p, err := net.ListenPacket("udp", ":53") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | defer p.Close() 17 | 18 | for { 19 | buf := make([]byte, 512) 20 | n, addr, err := p.ReadFrom(buf) 21 | if err != nil { 22 | fmt.Printf("Connection error [%s]: %s\n", addr.String(), err) 23 | continue 24 | } 25 | go dns.HandlePacket(p, addr, buf[:n]) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dns-demo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/dns-demo 2 | 3 | go 1.19 4 | 5 | require golang.org/x/net v0.1.0 // indirect 6 | -------------------------------------------------------------------------------- /dns-demo/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= 2 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 3 | -------------------------------------------------------------------------------- /dns-demo/pkg/dns/resolver_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/rand" 5 | "math/big" 6 | "net" 7 | "strings" 8 | "testing" 9 | "time" 10 | 11 | "golang.org/x/net/dns/dnsmessage" 12 | ) 13 | 14 | type MockPacketConn struct{} 15 | 16 | func (m *MockPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 17 | return 0, nil 18 | } 19 | 20 | func (m *MockPacketConn) Close() error { 21 | return nil 22 | } 23 | 24 | func (m *MockPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 25 | return 0, nil, nil 26 | } 27 | func (m *MockPacketConn) LocalAddr() net.Addr { 28 | return nil 29 | } 30 | func (m *MockPacketConn) SetDeadline(t time.Time) error { 31 | return nil 32 | } 33 | func (m *MockPacketConn) SetReadDeadline(t time.Time) error { 34 | return nil 35 | } 36 | func (m *MockPacketConn) SetWriteDeadline(t time.Time) error { 37 | return nil 38 | } 39 | 40 | func TestHandlePacket(t *testing.T) { 41 | names := []string{"www.google.com.", "www.amazon.com."} 42 | for _, name := range names { 43 | max := ^uint16(0) 44 | randomNumber, err := rand.Int(rand.Reader, big.NewInt(int64(max))) 45 | if err != nil { 46 | t.Fatalf("rand error: %s", err) 47 | } 48 | message := dnsmessage.Message{ 49 | Header: dnsmessage.Header{ 50 | RCode: dnsmessage.RCode(0), 51 | ID: uint16(randomNumber.Int64()), 52 | OpCode: dnsmessage.OpCode(0), 53 | Response: false, 54 | AuthenticData: false, 55 | RecursionDesired: false, 56 | }, 57 | Questions: []dnsmessage.Question{ 58 | { 59 | Name: dnsmessage.MustNewName(name), 60 | Type: dnsmessage.TypeA, 61 | Class: dnsmessage.ClassINET, 62 | }, 63 | }, 64 | } 65 | buf, err := message.Pack() 66 | if err != nil { 67 | t.Fatalf("Pack error: %s", err) 68 | } 69 | 70 | err = handlePacket(&MockPacketConn{}, &net.IPAddr{IP: net.ParseIP("127.0.0.1")}, buf) 71 | if err != nil { 72 | t.Fatalf("serve error: %s", err) 73 | } 74 | } 75 | } 76 | 77 | func TestOutgoingDnsQuery(t *testing.T) { 78 | question := dnsmessage.Question{ 79 | Name: dnsmessage.MustNewName("com."), 80 | Type: dnsmessage.TypeNS, 81 | Class: dnsmessage.ClassINET, 82 | } 83 | rootServers := strings.Split(ROOT_SERVERS, ",") 84 | if len(rootServers) == 0 { 85 | t.Fatalf("No root servers found") 86 | } 87 | servers := []net.IP{net.ParseIP(rootServers[0])} 88 | dnsAnswer, header, err := outgoingDnsQuery(servers, question) 89 | if err != nil { 90 | t.Fatalf("outgoingDnsQuery error: %s", err) 91 | } 92 | if header == nil { 93 | t.Fatalf("No header found") 94 | } 95 | if dnsAnswer == nil { 96 | t.Fatalf("no answer found") 97 | } 98 | if header.RCode != dnsmessage.RCodeSuccess { 99 | t.Fatalf("response was not succesful (maybe the DNS server has changed?)") 100 | } 101 | err = dnsAnswer.SkipAllAnswers() 102 | if err != nil { 103 | t.Fatalf("SkipAllAnswers error: %s", err) 104 | } 105 | parsedAuthorities, err := dnsAnswer.AllAuthorities() 106 | if err != nil { 107 | t.Fatalf("Error getting answers") 108 | } 109 | if len(parsedAuthorities) == 0 { 110 | t.Fatalf("No answers received") 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /dns-start/cmd/dns-resolver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Printf("Starting DNS Server...\n") 7 | } 8 | -------------------------------------------------------------------------------- /dns-start/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/dns-start 2 | 3 | go 1.19 4 | 5 | require golang.org/x/net v0.2.0 6 | -------------------------------------------------------------------------------- /dns-start/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= 2 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 3 | -------------------------------------------------------------------------------- /dns-start/pkg/dns/resolver.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "golang.org/x/net/dns/dnsmessage" 8 | ) 9 | 10 | const ROOT_SERVERS = "198.41.0.4,199.9.14.201,192.33.4.12,199.7.91.13,192.203.230.10,192.5.5.241,192.112.36.4,198.97.190.53" 11 | 12 | func handlePacket(pc net.PacketConn, addr net.Addr, buf []byte) error { 13 | return fmt.Errorf("not implemented yet") 14 | } 15 | 16 | func outgoingDnsQuery(servers []net.IP, question dnsmessage.Question) (*dnsmessage.Parser, *dnsmessage.Header, error) { 17 | return nil, nil, nil 18 | } 19 | -------------------------------------------------------------------------------- /dns-start/pkg/dns/resolver_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/rand" 5 | "math/big" 6 | "net" 7 | "strings" 8 | "testing" 9 | "time" 10 | 11 | "golang.org/x/net/dns/dnsmessage" 12 | ) 13 | 14 | type MockPacketConn struct{} 15 | 16 | func (m *MockPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 17 | return 0, nil 18 | } 19 | 20 | func (m *MockPacketConn) Close() error { 21 | return nil 22 | } 23 | 24 | func (m *MockPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 25 | return 0, nil, nil 26 | } 27 | func (m *MockPacketConn) LocalAddr() net.Addr { 28 | return nil 29 | } 30 | func (m *MockPacketConn) SetDeadline(t time.Time) error { 31 | return nil 32 | } 33 | func (m *MockPacketConn) SetReadDeadline(t time.Time) error { 34 | return nil 35 | } 36 | func (m *MockPacketConn) SetWriteDeadline(t time.Time) error { 37 | return nil 38 | } 39 | 40 | func TestHandlePacket(t *testing.T) { 41 | names := []string{"www.google.com.", "www.amazon.com."} 42 | for _, name := range names { 43 | max := ^uint16(0) 44 | randomNumber, err := rand.Int(rand.Reader, big.NewInt(int64(max))) 45 | if err != nil { 46 | t.Fatalf("rand error: %s", err) 47 | } 48 | message := dnsmessage.Message{ 49 | Header: dnsmessage.Header{ 50 | RCode: dnsmessage.RCode(0), 51 | ID: uint16(randomNumber.Int64()), 52 | OpCode: dnsmessage.OpCode(0), 53 | Response: false, 54 | AuthenticData: false, 55 | RecursionDesired: false, 56 | }, 57 | Questions: []dnsmessage.Question{ 58 | { 59 | Name: dnsmessage.MustNewName(name), 60 | Type: dnsmessage.TypeA, 61 | Class: dnsmessage.ClassINET, 62 | }, 63 | }, 64 | } 65 | buf, err := message.Pack() 66 | if err != nil { 67 | t.Fatalf("Pack error: %s", err) 68 | } 69 | 70 | err = handlePacket(&MockPacketConn{}, &net.IPAddr{IP: net.ParseIP("127.0.0.1")}, buf) 71 | if err != nil { 72 | t.Fatalf("serve error: %s", err) 73 | } 74 | } 75 | } 76 | 77 | func TestOutgoingDnsQuery(t *testing.T) { 78 | question := dnsmessage.Question{ 79 | Name: dnsmessage.MustNewName("com."), 80 | Type: dnsmessage.TypeNS, 81 | Class: dnsmessage.ClassINET, 82 | } 83 | 84 | if len(ROOT_SERVERS) == 0 { 85 | t.Fatalf("No root servers found") 86 | } 87 | 88 | rootServers := strings.Split(ROOT_SERVERS, ",") 89 | 90 | servers := []net.IP{net.ParseIP(rootServers[0])} 91 | dnsAnswer, header, err := outgoingDnsQuery(servers, question) 92 | if err != nil { 93 | t.Fatalf("outgoingDnsQuery error: %s", err) 94 | } 95 | if header == nil { 96 | t.Fatalf("No header found") 97 | } 98 | if dnsAnswer == nil { 99 | t.Fatalf("no answer found") 100 | } 101 | if header.RCode != dnsmessage.RCodeSuccess { 102 | t.Fatalf("response was not succesful (maybe the DNS server has changed?)") 103 | } 104 | err = dnsAnswer.SkipAllAnswers() 105 | if err != nil { 106 | t.Fatalf("SkipAllAnswers error: %s", err) 107 | } 108 | parsedAuthorities, err := dnsAnswer.AllAuthorities() 109 | if err != nil { 110 | t.Fatalf("Error getting answers") 111 | } 112 | if len(parsedAuthorities) == 0 { 113 | t.Fatalf("No answers received") 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.18 2 | 3 | use ( 4 | ./aws-ec2 5 | ./aws-s3 6 | ./aws-s3-testing 7 | ./azure-instance 8 | ./dns-demo 9 | ./dns-start 10 | ./hello-world 11 | ./hello-world-arguments 12 | ./http-get 13 | ./http-get-errorhandling 14 | ./http-get-flags 15 | ./http-get-functions 16 | ./http-get-json 17 | ./http-get-json-map 18 | ./http-login 19 | ./http-login-packaged 20 | ./http-login-tests 21 | ./kubernetes-deploy 22 | ./kubernetes-deploy-github 23 | ./mutex-demo 24 | ./oidc-demo 25 | ./oidc-start 26 | ./reader-example 27 | ./slices-demo 28 | ./ssh-demo 29 | ./test-server 30 | ./tls-demo 31 | ./tls-start 32 | ./types-demo 33 | ) -------------------------------------------------------------------------------- /go.work.sum: -------------------------------------------------------------------------------- 1 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 2 | github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= 3 | github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= 4 | github.com/spf13/cobra-cli v1.3.0/go.mod h1:zq1KeHo/9SQm1tNdbJhwVDd9bVpokbQwuG6MR0TFCdE= 5 | github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= 6 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 7 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 8 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 9 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 10 | golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 11 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 12 | golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= 13 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 14 | golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= 15 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 16 | golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= 17 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 18 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 19 | google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= 20 | google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= 21 | -------------------------------------------------------------------------------- /hello-world-arguments/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/go-for-devops-course/hello-world-arguments 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /hello-world-arguments/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | args := os.Args 10 | 11 | if len(args) < 2 { 12 | fmt.Printf("Usage: ./hello-world-arguments \n") 13 | os.Exit(1) 14 | } 15 | 16 | fmt.Printf("hello world!\nos.Args: %v\nArguments: %v\n", args, args[1:]) 17 | } 18 | -------------------------------------------------------------------------------- /hello-world/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/go-for-devops-course/hello-world 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /hello-world/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Printf("hello world!") 7 | } 8 | -------------------------------------------------------------------------------- /http-get-errorhandling/error.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type RequestError struct { 4 | Body string 5 | HTTPCode int 6 | Err string 7 | } 8 | 9 | func (r RequestError) Error() string { 10 | return r.Err 11 | } 12 | -------------------------------------------------------------------------------- /http-get-errorhandling/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/http-get-errorhandling 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /http-get-errorhandling/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | type Page struct { 14 | Name string `json:"page"` 15 | } 16 | 17 | type Response interface { 18 | GetResponse() string 19 | } 20 | 21 | type Words struct { 22 | Input string `json:"input"` 23 | Words []string `json:"words"` 24 | } 25 | 26 | func (w Words) GetResponse() string { 27 | return fmt.Sprintf("Words: %s", strings.Join(w.Words, ", ")) 28 | } 29 | 30 | type Occurrence struct { 31 | Words map[string]int `json:"words"` 32 | } 33 | 34 | func (o Occurrence) GetResponse() string { 35 | words := []string{} 36 | for word, occurrence := range o.Words { 37 | words = append(words, fmt.Sprintf("%s (%d)", word, occurrence)) 38 | } 39 | return fmt.Sprintf("Words: %s", strings.Join(words, ", ")) 40 | } 41 | 42 | func main() { 43 | args := os.Args 44 | 45 | if len(args) < 2 { 46 | fmt.Printf("Usage: ./http-get \n") 47 | os.Exit(1) 48 | } 49 | 50 | res, err := doRequest(args[1]) 51 | if err != nil { 52 | if requestErr, ok := err.(RequestError); ok { 53 | fmt.Printf("Error occurred: %s (HTTP Error: %d, Body: %s)\n", requestErr.Error(), requestErr.HTTPCode, requestErr.Body) 54 | os.Exit(1) 55 | } 56 | fmt.Printf("Error occurred: %s\n", err) 57 | os.Exit(1) 58 | } 59 | if res == nil { 60 | fmt.Printf("No response\n") 61 | os.Exit(1) 62 | } 63 | fmt.Printf("Response: %s\n", res.GetResponse()) 64 | } 65 | 66 | func doRequest(requestURL string) (Response, error) { 67 | 68 | if _, err := url.ParseRequestURI(requestURL); err != nil { 69 | fmt.Printf("Usage: ./http-get \n\nURL is not valid URL: %s\n", requestURL) 70 | os.Exit(1) 71 | } 72 | 73 | response, err := http.Get(requestURL) 74 | 75 | if err != nil { 76 | return nil, fmt.Errorf("Get error: %s", err) 77 | } 78 | 79 | defer response.Body.Close() 80 | 81 | body, err := io.ReadAll(response.Body) 82 | 83 | if err != nil { 84 | return nil, fmt.Errorf("ReadAll error: %s", err) 85 | } 86 | 87 | if response.StatusCode != 200 { 88 | return nil, fmt.Errorf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(body)) 89 | } 90 | 91 | var page Page 92 | 93 | if !json.Valid(body) { 94 | return nil, RequestError{ 95 | Err: fmt.Sprintf("Response is not a json"), 96 | HTTPCode: response.StatusCode, 97 | Body: string(body), 98 | } 99 | } 100 | 101 | err = json.Unmarshal(body, &page) 102 | if err != nil { 103 | return nil, RequestError{ 104 | Err: fmt.Sprintf("Page unmarshal error: %s", err), 105 | HTTPCode: response.StatusCode, 106 | Body: string(body), 107 | } 108 | } 109 | 110 | switch page.Name { 111 | case "words": 112 | var words Words 113 | err = json.Unmarshal(body, &words) 114 | if err != nil { 115 | return nil, fmt.Errorf("Words unmarshal error: %s", err) 116 | } 117 | 118 | return words, nil 119 | case "occurrence": 120 | var occurrence Occurrence 121 | err = json.Unmarshal(body, &occurrence) 122 | if err != nil { 123 | return nil, fmt.Errorf("Occurrence unmarshal error: %s", err) 124 | } 125 | 126 | return occurrence, nil 127 | } 128 | 129 | return nil, nil 130 | } 131 | -------------------------------------------------------------------------------- /http-get-flags/error.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type RequestError struct { 4 | Body string 5 | HTTPCode int 6 | Err string 7 | } 8 | 9 | func (r RequestError) Error() string { 10 | return r.Err 11 | } 12 | -------------------------------------------------------------------------------- /http-get-flags/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/http-get-flags 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /http-get-flags/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | type Page struct { 15 | Name string `json:"page"` 16 | } 17 | 18 | type Response interface { 19 | GetResponse() string 20 | } 21 | 22 | type Words struct { 23 | Input string `json:"input"` 24 | Words []string `json:"words"` 25 | } 26 | 27 | func (w Words) GetResponse() string { 28 | return fmt.Sprintf("Words: %s", strings.Join(w.Words, ", ")) 29 | } 30 | 31 | type Occurrence struct { 32 | Words map[string]int `json:"words"` 33 | } 34 | 35 | func (o Occurrence) GetResponse() string { 36 | words := []string{} 37 | for word, occurrence := range o.Words { 38 | words = append(words, fmt.Sprintf("%s (%d)", word, occurrence)) 39 | } 40 | return fmt.Sprintf("Words: %s", strings.Join(words, ", ")) 41 | } 42 | 43 | func main() { 44 | var ( 45 | requestURL string 46 | password string 47 | parsedURL *url.URL 48 | err error 49 | ) 50 | flag.StringVar(&requestURL, "url", "", "url to access") 51 | flag.StringVar(&password, "password", "", "use a password to access our api") 52 | 53 | flag.Parse() 54 | 55 | if parsedURL, err = url.ParseRequestURI(requestURL); err != nil { 56 | fmt.Printf("Help: ./http-get -h\nURL is not valid URL: %s\n", requestURL) 57 | os.Exit(1) 58 | } 59 | 60 | res, err := doRequest(parsedURL.String()) 61 | if err != nil { 62 | if requestErr, ok := err.(RequestError); ok { 63 | fmt.Printf("Error occurred: %s (HTTP Error: %d, Body: %s)\n", requestErr.Error(), requestErr.HTTPCode, requestErr.Body) 64 | os.Exit(1) 65 | } 66 | fmt.Printf("Error occurred: %s\n", err) 67 | os.Exit(1) 68 | } 69 | if res == nil { 70 | fmt.Printf("No response\n") 71 | os.Exit(1) 72 | } 73 | fmt.Printf("Response: %s\n", res.GetResponse()) 74 | } 75 | 76 | func doRequest(requestURL string) (Response, error) { 77 | 78 | response, err := http.Get(requestURL) 79 | 80 | if err != nil { 81 | return nil, fmt.Errorf("Get error: %s", err) 82 | } 83 | 84 | defer response.Body.Close() 85 | 86 | body, err := io.ReadAll(response.Body) 87 | 88 | if err != nil { 89 | return nil, fmt.Errorf("ReadAll error: %s", err) 90 | } 91 | 92 | if response.StatusCode != 200 { 93 | return nil, fmt.Errorf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(body)) 94 | } 95 | 96 | var page Page 97 | 98 | if !json.Valid(body) { 99 | return nil, RequestError{ 100 | Err: fmt.Sprintf("Response is not a json"), 101 | HTTPCode: response.StatusCode, 102 | Body: string(body), 103 | } 104 | } 105 | 106 | err = json.Unmarshal(body, &page) 107 | if err != nil { 108 | return nil, RequestError{ 109 | Err: fmt.Sprintf("Page unmarshal error: %s", err), 110 | HTTPCode: response.StatusCode, 111 | Body: string(body), 112 | } 113 | } 114 | 115 | switch page.Name { 116 | case "words": 117 | var words Words 118 | err = json.Unmarshal(body, &words) 119 | if err != nil { 120 | return nil, fmt.Errorf("Words unmarshal error: %s", err) 121 | } 122 | 123 | return words, nil 124 | case "occurrence": 125 | var occurrence Occurrence 126 | err = json.Unmarshal(body, &occurrence) 127 | if err != nil { 128 | return nil, fmt.Errorf("Occurrence unmarshal error: %s", err) 129 | } 130 | 131 | return occurrence, nil 132 | } 133 | 134 | return nil, nil 135 | } 136 | -------------------------------------------------------------------------------- /http-get-functions/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/http-get-functions 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /http-get-functions/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | type Page struct { 14 | Name string `json:"page"` 15 | } 16 | 17 | type Response interface { 18 | GetResponse() string 19 | } 20 | 21 | type Words struct { 22 | Input string `json:"input"` 23 | Words []string `json:"words"` 24 | } 25 | 26 | func (w Words) GetResponse() string { 27 | return fmt.Sprintf("Words: %s", strings.Join(w.Words, ", ")) 28 | } 29 | 30 | type Occurrence struct { 31 | Words map[string]int `json:"words"` 32 | } 33 | 34 | func (o Occurrence) GetResponse() string { 35 | words := []string{} 36 | for word, occurrence := range o.Words { 37 | words = append(words, fmt.Sprintf("%s (%d)", word, occurrence)) 38 | } 39 | return fmt.Sprintf("Words: %s", strings.Join(words, ", ")) 40 | } 41 | 42 | func main() { 43 | args := os.Args 44 | 45 | if len(args) < 2 { 46 | fmt.Printf("Usage: ./http-get \n") 47 | os.Exit(1) 48 | } 49 | 50 | res, err := doRequest(args[1]) 51 | if err != nil { 52 | fmt.Printf("Error occurred: %s\n", err) 53 | os.Exit(1) 54 | } 55 | if res == nil { 56 | fmt.Printf("No response\n") 57 | os.Exit(1) 58 | } 59 | fmt.Printf("Response: %s\n", res.GetResponse()) 60 | } 61 | 62 | func doRequest(requestURL string) (Response, error) { 63 | 64 | if _, err := url.ParseRequestURI(requestURL); err != nil { 65 | fmt.Printf("Usage: ./http-get \n\nURL is not valid URL: %s\n", requestURL) 66 | os.Exit(1) 67 | } 68 | 69 | response, err := http.Get(requestURL) 70 | 71 | if err != nil { 72 | return nil, fmt.Errorf("Get error: %s", err) 73 | } 74 | 75 | defer response.Body.Close() 76 | 77 | body, err := io.ReadAll(response.Body) 78 | 79 | if err != nil { 80 | return nil, fmt.Errorf("ReadAll error: %s", err) 81 | } 82 | 83 | if response.StatusCode != 200 { 84 | return nil, fmt.Errorf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(body)) 85 | } 86 | 87 | var page Page 88 | 89 | err = json.Unmarshal(body, &page) 90 | if err != nil { 91 | return nil, fmt.Errorf("unmarshal error: %s", err) 92 | } 93 | 94 | switch page.Name { 95 | case "words": 96 | var words Words 97 | err = json.Unmarshal(body, &words) 98 | if err != nil { 99 | return nil, fmt.Errorf("unmarshal error: %s", err) 100 | } 101 | 102 | return words, nil 103 | case "occurrence": 104 | var occurrence Occurrence 105 | err = json.Unmarshal(body, &occurrence) 106 | if err != nil { 107 | return nil, fmt.Errorf("unmarshal error: %s", err) 108 | } 109 | 110 | return occurrence, nil 111 | } 112 | 113 | return nil, nil 114 | } 115 | -------------------------------------------------------------------------------- /http-get-json-map/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/go-for-devops-course/http-get-json-map 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /http-get-json-map/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | type Page struct { 15 | Name string `json:"page"` 16 | } 17 | 18 | type Words struct { 19 | Input string `json:"input"` 20 | Words []string `json:"words"` 21 | } 22 | 23 | type Occurrence struct { 24 | Words map[string]int `json:"words"` 25 | } 26 | 27 | func main() { 28 | args := os.Args 29 | 30 | if len(args) < 2 { 31 | fmt.Printf("Usage: ./http-get \n") 32 | os.Exit(1) 33 | } 34 | 35 | if _, err := url.ParseRequestURI(args[1]); err != nil { 36 | fmt.Printf("Usage: ./http-get \n\nURL is not valid URL: %s\n", args[1]) 37 | os.Exit(1) 38 | } 39 | 40 | response, err := http.Get(args[1]) 41 | 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | defer response.Body.Close() 47 | 48 | body, err := io.ReadAll(response.Body) 49 | 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | if response.StatusCode != 200 { 55 | fmt.Printf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(body)) 56 | os.Exit(1) 57 | } 58 | 59 | var page Page 60 | 61 | err = json.Unmarshal(body, &page) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | 66 | switch page.Name { 67 | case "words": 68 | var words Words 69 | err = json.Unmarshal(body, &words) 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | fmt.Printf("JSON: Parsed:\nPage: %s\nWords: %s\n", page.Name, strings.Join(words.Words, ", ")) 74 | case "occurrence": 75 | var occurrence Occurrence 76 | err = json.Unmarshal(body, &occurrence) 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | 81 | if val, ok := occurrence.Words["word5"]; ok { 82 | fmt.Printf("Found word1: %d\n", val) 83 | } 84 | 85 | for word, occurrence := range occurrence.Words { 86 | fmt.Printf("%s: %d\n", word, occurrence) 87 | } 88 | 89 | default: 90 | fmt.Printf("Page not found\n") 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /http-get-json/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/go-for-devops-course/http-get-json 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /http-get-json/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | type Words struct { 15 | Page string `json:"page"` 16 | Input string `json:"input"` 17 | Words []string `json:"words"` 18 | } 19 | 20 | func main() { 21 | args := os.Args 22 | 23 | if len(args) < 2 { 24 | fmt.Printf("Usage: ./http-get \n") 25 | os.Exit(1) 26 | } 27 | 28 | if _, err := url.ParseRequestURI(args[1]); err != nil { 29 | fmt.Printf("Usage: ./http-get \n\nURL is not valid URL: %s\n", args[1]) 30 | os.Exit(1) 31 | } 32 | 33 | response, err := http.Get(args[1]) 34 | 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | defer response.Body.Close() 40 | 41 | body, err := io.ReadAll(response.Body) 42 | 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | if response.StatusCode != 200 { 48 | fmt.Printf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(body)) 49 | os.Exit(1) 50 | } 51 | 52 | var words Words 53 | 54 | err = json.Unmarshal(body, &words) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | 59 | fmt.Printf("JSON: Parsed:\nPage: %s\nWords: %s\n", words.Page, strings.Join(words.Words, ", ")) 60 | } 61 | -------------------------------------------------------------------------------- /http-get/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/http-get 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /http-get/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | args := os.Args 14 | 15 | if len(args) < 2 { 16 | fmt.Printf("Usage: ./http-get \n") 17 | os.Exit(1) 18 | } 19 | 20 | if _, err := url.ParseRequestURI(args[1]); err != nil { 21 | fmt.Printf("Usage: ./http-get \n\nURL is not valid URL: %s\n", args[1]) 22 | os.Exit(1) 23 | } 24 | 25 | response, err := http.Get(args[1]) 26 | 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | defer response.Body.Close() 32 | 33 | body, err := io.ReadAll(response.Body) 34 | 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | fmt.Printf("HTTP Status Code: %d\nBody: %s\n", response.StatusCode, string(body)) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /http-login-packaged/cmd/http-login/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/url" 7 | "os" 8 | 9 | "github.com/wardviaene/golang-for-devops-course/http-login-packaged/pkg/api" 10 | ) 11 | 12 | func main() { 13 | var ( 14 | requestURL string 15 | password string 16 | parsedURL *url.URL 17 | err error 18 | ) 19 | flag.StringVar(&requestURL, "url", "", "url to access") 20 | flag.StringVar(&password, "password", "", "use a password to access our api") 21 | 22 | flag.Parse() 23 | 24 | if parsedURL, err = url.ParseRequestURI(requestURL); err != nil { 25 | fmt.Printf("Help: ./http-get -h\nURL is not valid URL: %s\n", requestURL) 26 | os.Exit(1) 27 | } 28 | 29 | apiInstance := api.New(api.Options{ 30 | Password: password, 31 | LoginURL: parsedURL.Scheme + "://" + parsedURL.Host + "/login", 32 | }) 33 | 34 | res, err := apiInstance.DoGetRequest(parsedURL.String()) 35 | if err != nil { 36 | if requestErr, ok := err.(api.RequestError); ok { 37 | fmt.Printf("Error occurred: %s (HTTP Error: %d, Body: %s)\n", requestErr.Error(), requestErr.HTTPCode, requestErr.Body) 38 | os.Exit(1) 39 | } 40 | fmt.Printf("Error occurred: %s\n", err) 41 | os.Exit(1) 42 | } 43 | if res == nil { 44 | fmt.Printf("No response\n") 45 | os.Exit(1) 46 | } 47 | fmt.Printf("Response: %s\n", res.GetResponse()) 48 | } 49 | -------------------------------------------------------------------------------- /http-login-packaged/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/http-login-packaged 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /http-login-packaged/pkg/api/error.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type RequestError struct { 4 | Body string 5 | HTTPCode int 6 | Err string 7 | } 8 | 9 | func (r RequestError) Error() string { 10 | return r.Err 11 | } 12 | -------------------------------------------------------------------------------- /http-login-packaged/pkg/api/get.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | type Page struct { 11 | Name string `json:"page"` 12 | } 13 | 14 | type Response interface { 15 | GetResponse() string 16 | } 17 | 18 | type Words struct { 19 | Input string `json:"input"` 20 | Words []string `json:"words"` 21 | } 22 | 23 | func (w Words) GetResponse() string { 24 | return fmt.Sprintf("Words: %s", strings.Join(w.Words, ", ")) 25 | } 26 | 27 | type Occurrence struct { 28 | Words map[string]int `json:"words"` 29 | } 30 | 31 | func (o Occurrence) GetResponse() string { 32 | words := []string{} 33 | for word, occurrence := range o.Words { 34 | words = append(words, fmt.Sprintf("%s (%d)", word, occurrence)) 35 | } 36 | return fmt.Sprintf("Words: %s", strings.Join(words, ", ")) 37 | } 38 | 39 | func (a api) DoGetRequest(requestURL string) (Response, error) { 40 | 41 | response, err := a.Client.Get(requestURL) 42 | 43 | if err != nil { 44 | return nil, fmt.Errorf("Get error: %s", err) 45 | } 46 | 47 | defer response.Body.Close() 48 | 49 | body, err := io.ReadAll(response.Body) 50 | 51 | if err != nil { 52 | return nil, fmt.Errorf("ReadAll error: %s", err) 53 | } 54 | 55 | if response.StatusCode != 200 { 56 | return nil, fmt.Errorf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(body)) 57 | } 58 | 59 | var page Page 60 | 61 | if !json.Valid(body) { 62 | return nil, RequestError{ 63 | Err: fmt.Sprintf("Response is not a json"), 64 | HTTPCode: response.StatusCode, 65 | Body: string(body), 66 | } 67 | } 68 | 69 | err = json.Unmarshal(body, &page) 70 | if err != nil { 71 | return nil, RequestError{ 72 | Err: fmt.Sprintf("Page unmarshal error: %s", err), 73 | HTTPCode: response.StatusCode, 74 | Body: string(body), 75 | } 76 | } 77 | 78 | switch page.Name { 79 | case "words": 80 | var words Words 81 | err = json.Unmarshal(body, &words) 82 | if err != nil { 83 | return nil, fmt.Errorf("Words unmarshal error: %s", err) 84 | } 85 | 86 | return words, nil 87 | case "occurrence": 88 | var occurrence Occurrence 89 | err = json.Unmarshal(body, &occurrence) 90 | if err != nil { 91 | return nil, fmt.Errorf("Occurrence unmarshal error: %s", err) 92 | } 93 | 94 | return occurrence, nil 95 | } 96 | 97 | return nil, nil 98 | } 99 | -------------------------------------------------------------------------------- /http-login-packaged/pkg/api/init.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "net/http" 4 | 5 | type Options struct { 6 | Password string 7 | LoginURL string 8 | } 9 | 10 | type APIIface interface { 11 | DoGetRequest(requestURL string) (Response, error) 12 | } 13 | 14 | type api struct { 15 | Options Options 16 | Client http.Client 17 | } 18 | 19 | func New(options Options) APIIface { 20 | return api{ 21 | Options: options, 22 | Client: http.Client{ 23 | Transport: &MyJWTTransport{ 24 | transport: http.DefaultTransport, 25 | password: options.Password, 26 | loginURL: options.LoginURL, 27 | }, 28 | }, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /http-login-packaged/pkg/api/login.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | type LoginRequest struct { 12 | Password string `json:"password"` 13 | } 14 | type LoginResponse struct { 15 | Token string `json:"token"` 16 | } 17 | 18 | func doLoginRequest(client http.Client, requestURL, password string) (string, error) { 19 | loginRequest := LoginRequest{ 20 | Password: password, 21 | } 22 | 23 | body, err := json.Marshal(loginRequest) 24 | if err != nil { 25 | return "", fmt.Errorf("Marshal error: %s", err) 26 | } 27 | 28 | response, err := client.Post(requestURL, "application/json", bytes.NewBuffer(body)) 29 | 30 | if err != nil { 31 | return "", fmt.Errorf("http Post error: %s", err) 32 | } 33 | 34 | defer response.Body.Close() 35 | 36 | resBody, err := io.ReadAll(response.Body) 37 | 38 | if err != nil { 39 | return "", fmt.Errorf("ReadAll error: %s", err) 40 | } 41 | 42 | if response.StatusCode != 200 { 43 | return "", fmt.Errorf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(resBody)) 44 | } 45 | 46 | if !json.Valid(resBody) { 47 | return "", RequestError{ 48 | HTTPCode: response.StatusCode, 49 | Body: string(resBody), 50 | Err: fmt.Sprintf("No valid JSON returned"), 51 | } 52 | } 53 | 54 | var loginResponse LoginResponse 55 | 56 | err = json.Unmarshal(resBody, &loginResponse) 57 | if err != nil { 58 | return "", RequestError{ 59 | HTTPCode: response.StatusCode, 60 | Body: string(resBody), 61 | Err: fmt.Sprintf("Page unmarshal error: %s", err), 62 | } 63 | } 64 | 65 | if loginResponse.Token == "" { 66 | return "", RequestError{ 67 | HTTPCode: response.StatusCode, 68 | Body: string(resBody), 69 | Err: "Empty token replied", 70 | } 71 | } 72 | 73 | return loginResponse.Token, nil 74 | } 75 | -------------------------------------------------------------------------------- /http-login-packaged/pkg/api/transport.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type MyJWTTransport struct { 8 | transport http.RoundTripper 9 | token string 10 | password string 11 | loginURL string 12 | } 13 | 14 | func (m *MyJWTTransport) RoundTrip(req *http.Request) (*http.Response, error) { 15 | if m.token == "" { 16 | if m.password != "" { 17 | token, err := doLoginRequest(http.Client{}, m.loginURL, m.password) 18 | if err != nil { 19 | return nil, err 20 | } 21 | m.token = token 22 | } 23 | } 24 | if m.token != "" { 25 | req.Header.Add("Authorization", "Bearer "+m.token) 26 | } 27 | return m.transport.RoundTrip(req) 28 | } 29 | -------------------------------------------------------------------------------- /http-login-tests/cmd/http-login/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/url" 7 | "os" 8 | 9 | "github.com/wardviaene/golang-for-devops-course/http-login-tests/pkg/api" 10 | ) 11 | 12 | func main() { 13 | var ( 14 | requestURL string 15 | password string 16 | parsedURL *url.URL 17 | err error 18 | ) 19 | flag.StringVar(&requestURL, "url", "", "url to access") 20 | flag.StringVar(&password, "password", "", "use a password to access our api") 21 | 22 | flag.Parse() 23 | 24 | if parsedURL, err = url.ParseRequestURI(requestURL); err != nil { 25 | fmt.Printf("Help: ./http-get -h\nURL is not valid URL: %s\n", requestURL) 26 | os.Exit(1) 27 | } 28 | 29 | apiInstance := api.New(api.Options{ 30 | Password: password, 31 | LoginURL: parsedURL.Scheme + "://" + parsedURL.Host + "/login", 32 | }) 33 | 34 | res, err := apiInstance.DoGetRequest(parsedURL.String()) 35 | if err != nil { 36 | if requestErr, ok := err.(api.RequestError); ok { 37 | fmt.Printf("Error occurred: %s (HTTP Error: %d, Body: %s)\n", requestErr.Error(), requestErr.HTTPCode, requestErr.Body) 38 | os.Exit(1) 39 | } 40 | fmt.Printf("Error occurred: %s\n", err) 41 | os.Exit(1) 42 | } 43 | if res == nil { 44 | fmt.Printf("No response\n") 45 | os.Exit(1) 46 | } 47 | fmt.Printf("Response: %s\n", res.GetResponse()) 48 | } 49 | -------------------------------------------------------------------------------- /http-login-tests/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/http-login-tests 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /http-login-tests/pkg/api/error.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type RequestError struct { 4 | Body string 5 | HTTPCode int 6 | Err string 7 | } 8 | 9 | func (r RequestError) Error() string { 10 | return r.Err 11 | } 12 | -------------------------------------------------------------------------------- /http-login-tests/pkg/api/get.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | type Page struct { 11 | Name string `json:"page"` 12 | } 13 | 14 | type Response interface { 15 | GetResponse() string 16 | } 17 | 18 | type Words struct { 19 | Input string `json:"input"` 20 | Words []string `json:"words"` 21 | } 22 | 23 | type WordsPage struct { 24 | Page 25 | Words 26 | } 27 | 28 | func (w Words) GetResponse() string { 29 | return fmt.Sprintf("Words: %s", strings.Join(w.Words, ", ")) 30 | } 31 | 32 | type Occurrence struct { 33 | Words map[string]int `json:"words"` 34 | } 35 | 36 | func (o Occurrence) GetResponse() string { 37 | words := []string{} 38 | for word, occurrence := range o.Words { 39 | words = append(words, fmt.Sprintf("%s (%d)", word, occurrence)) 40 | } 41 | return fmt.Sprintf("Words: %s", strings.Join(words, ", ")) 42 | } 43 | 44 | func (a api) DoGetRequest(requestURL string) (Response, error) { 45 | 46 | response, err := a.Client.Get(requestURL) 47 | 48 | if err != nil { 49 | return nil, fmt.Errorf("Get error: %s", err) 50 | } 51 | 52 | defer response.Body.Close() 53 | 54 | body, err := io.ReadAll(response.Body) 55 | 56 | if err != nil { 57 | return nil, fmt.Errorf("ReadAll error: %s", err) 58 | } 59 | 60 | if response.StatusCode != 200 { 61 | return nil, fmt.Errorf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(body)) 62 | } 63 | 64 | var page Page 65 | 66 | if !json.Valid(body) { 67 | return nil, RequestError{ 68 | Err: fmt.Sprintf("Response is not a json"), 69 | HTTPCode: response.StatusCode, 70 | Body: string(body), 71 | } 72 | } 73 | 74 | err = json.Unmarshal(body, &page) 75 | if err != nil { 76 | return nil, RequestError{ 77 | Err: fmt.Sprintf("Page unmarshal error: %s", err), 78 | HTTPCode: response.StatusCode, 79 | Body: string(body), 80 | } 81 | } 82 | 83 | switch page.Name { 84 | case "words": 85 | var words Words 86 | err = json.Unmarshal(body, &words) 87 | if err != nil { 88 | return nil, fmt.Errorf("Words unmarshal error: %s", err) 89 | } 90 | 91 | return words, nil 92 | case "occurrence": 93 | var occurrence Occurrence 94 | err = json.Unmarshal(body, &occurrence) 95 | if err != nil { 96 | return nil, fmt.Errorf("Occurrence unmarshal error: %s", err) 97 | } 98 | 99 | return occurrence, nil 100 | } 101 | 102 | return nil, nil 103 | } 104 | -------------------------------------------------------------------------------- /http-login-tests/pkg/api/get_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | type MockClient struct { 13 | GetResponse *http.Response 14 | PostResponse *http.Response 15 | } 16 | 17 | func (m MockClient) Get(url string) (resp *http.Response, err error) { 18 | if url == "http://localhost/login" { 19 | fmt.Printf("Login endpoint") 20 | } 21 | return m.GetResponse, nil 22 | } 23 | 24 | func (m MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { 25 | return m.PostResponse, nil 26 | } 27 | 28 | func TestDoGetRequest(t *testing.T) { 29 | words := WordsPage{ 30 | Page: Page{"words"}, 31 | Words: Words{ 32 | Input: "abc", 33 | Words: []string{"a", "b"}, 34 | }, 35 | } 36 | wordsBytes, err := json.Marshal(words) 37 | if err != nil { 38 | t.Errorf("marshal error: %s", err) 39 | } 40 | apiInstance := api{ 41 | Options: Options{}, 42 | Client: MockClient{ 43 | GetResponse: &http.Response{ 44 | StatusCode: 200, 45 | Body: io.NopCloser(bytes.NewReader(wordsBytes)), 46 | }, 47 | }, 48 | } 49 | 50 | response, err := apiInstance.DoGetRequest("http://localhost/words") 51 | if err != nil { 52 | t.Errorf("DoGetRequest error: %s", err) 53 | } 54 | if response == nil { 55 | t.Errorf("Response is nil") 56 | } 57 | if response.GetResponse() != `Words: a, b` { 58 | t.Errorf("Got wrong output: %s", response.GetResponse()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /http-login-tests/pkg/api/init.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | ) 7 | 8 | type Options struct { 9 | Password string 10 | LoginURL string 11 | } 12 | 13 | type ClientIface interface { 14 | Get(url string) (resp *http.Response, err error) 15 | Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) 16 | } 17 | 18 | type APIIface interface { 19 | DoGetRequest(requestURL string) (Response, error) 20 | } 21 | 22 | type api struct { 23 | Options Options 24 | Client ClientIface 25 | } 26 | 27 | func New(options Options) APIIface { 28 | return api{ 29 | Options: options, 30 | Client: &http.Client{ 31 | Transport: MyJWTTransport{ 32 | transport: http.DefaultTransport, 33 | password: options.Password, 34 | loginURL: options.LoginURL, 35 | HTTPClient: &http.Client{}, 36 | }, 37 | }, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /http-login-tests/pkg/api/login.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | type LoginRequest struct { 11 | Password string `json:"password"` 12 | } 13 | type LoginResponse struct { 14 | Token string `json:"token"` 15 | } 16 | 17 | func doLoginRequest(client ClientIface, requestURL, password string) (string, error) { 18 | loginRequest := LoginRequest{ 19 | Password: password, 20 | } 21 | 22 | body, err := json.Marshal(loginRequest) 23 | if err != nil { 24 | return "", fmt.Errorf("Marshal error: %s", err) 25 | } 26 | 27 | response, err := client.Post(requestURL, "application/json", bytes.NewBuffer(body)) 28 | 29 | if err != nil { 30 | return "", fmt.Errorf("http Post error: %s", err) 31 | } 32 | 33 | defer response.Body.Close() 34 | 35 | resBody, err := io.ReadAll(response.Body) 36 | 37 | if err != nil { 38 | return "", fmt.Errorf("ReadAll error: %s", err) 39 | } 40 | 41 | if response.StatusCode != 200 { 42 | return "", fmt.Errorf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(resBody)) 43 | } 44 | 45 | if !json.Valid(resBody) { 46 | return "", RequestError{ 47 | HTTPCode: response.StatusCode, 48 | Body: string(resBody), 49 | Err: fmt.Sprintf("No valid JSON returned"), 50 | } 51 | } 52 | 53 | var loginResponse LoginResponse 54 | 55 | err = json.Unmarshal(resBody, &loginResponse) 56 | if err != nil { 57 | return "", RequestError{ 58 | HTTPCode: response.StatusCode, 59 | Body: string(resBody), 60 | Err: fmt.Sprintf("Page unmarshal error: %s", err), 61 | } 62 | } 63 | 64 | if loginResponse.Token == "" { 65 | return "", RequestError{ 66 | HTTPCode: response.StatusCode, 67 | Body: string(resBody), 68 | Err: "Empty token replied", 69 | } 70 | } 71 | 72 | return loginResponse.Token, nil 73 | } 74 | -------------------------------------------------------------------------------- /http-login-tests/pkg/api/transport.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type MyJWTTransport struct { 8 | transport http.RoundTripper 9 | token string 10 | password string 11 | loginURL string 12 | HTTPClient ClientIface 13 | } 14 | 15 | func (m MyJWTTransport) RoundTrip(req *http.Request) (*http.Response, error) { 16 | if m.token == "" { 17 | if m.password != "" { 18 | token, err := doLoginRequest(m.HTTPClient, m.loginURL, m.password) 19 | if err != nil { 20 | return nil, err 21 | } 22 | m.token = token 23 | } 24 | } 25 | if m.token != "" { 26 | req.Header.Add("Authorization", "Bearer "+m.token) 27 | } 28 | return m.transport.RoundTrip(req) 29 | } 30 | -------------------------------------------------------------------------------- /http-login-tests/pkg/api/transport_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | type MockRoundTripper struct { 13 | RoundTripOutput *http.Response 14 | } 15 | 16 | func (m MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 17 | if req.Header.Get("Authorization") != "Bearer 123" { 18 | return nil, fmt.Errorf("wrong Authorization header: %s", req.Header.Get("Authorization")) 19 | } 20 | return m.RoundTripOutput, nil 21 | } 22 | 23 | func TestRoundtrip(t *testing.T) { 24 | loginResponse := LoginResponse{ 25 | Token: "123", 26 | } 27 | loginResponseBytes, err := json.Marshal(loginResponse) 28 | if err != nil { 29 | t.Errorf("marshal error: %s", err) 30 | } 31 | 32 | jwtTransport := MyJWTTransport{ 33 | HTTPClient: MockClient{ 34 | PostResponse: &http.Response{ 35 | StatusCode: 200, 36 | Body: io.NopCloser(bytes.NewReader(loginResponseBytes)), 37 | }, 38 | }, 39 | transport: MockRoundTripper{ 40 | RoundTripOutput: &http.Response{ 41 | StatusCode: 200, 42 | }, 43 | }, 44 | password: "xyz", 45 | } 46 | req := &http.Request{ 47 | Header: make(http.Header), 48 | } 49 | res, err := jwtTransport.RoundTrip(req) 50 | if err != nil { 51 | t.Errorf("got error: %s", err) 52 | t.FailNow() 53 | } 54 | if res.StatusCode != 200 { 55 | t.Errorf("expected status code 200, got %d", res.StatusCode) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /http-login/error.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type RequestError struct { 4 | Body string 5 | HTTPCode int 6 | Err string 7 | } 8 | 9 | func (r RequestError) Error() string { 10 | return r.Err 11 | } 12 | -------------------------------------------------------------------------------- /http-login/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/http-login 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /http-login/login.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | type LoginRequest struct { 12 | Password string `json:"password"` 13 | } 14 | type LoginResponse struct { 15 | Token string `json:"token"` 16 | } 17 | 18 | func doLoginRequest(client http.Client, requestURL, password string) (string, error) { 19 | loginRequest := LoginRequest{ 20 | Password: password, 21 | } 22 | 23 | body, err := json.Marshal(loginRequest) 24 | if err != nil { 25 | return "", fmt.Errorf("Marshal error: %s", err) 26 | } 27 | 28 | response, err := client.Post(requestURL, "application/json", bytes.NewBuffer(body)) 29 | 30 | if err != nil { 31 | return "", fmt.Errorf("http Post error: %s", err) 32 | } 33 | 34 | defer response.Body.Close() 35 | 36 | resBody, err := io.ReadAll(response.Body) 37 | 38 | if err != nil { 39 | return "", fmt.Errorf("ReadAll error: %s", err) 40 | } 41 | 42 | if response.StatusCode != 200 { 43 | return "", fmt.Errorf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(resBody)) 44 | } 45 | 46 | if !json.Valid(resBody) { 47 | return "", RequestError{ 48 | HTTPCode: response.StatusCode, 49 | Body: string(resBody), 50 | Err: fmt.Sprintf("No valid JSON returned"), 51 | } 52 | } 53 | 54 | var loginResponse LoginResponse 55 | 56 | err = json.Unmarshal(resBody, &loginResponse) 57 | if err != nil { 58 | return "", RequestError{ 59 | HTTPCode: response.StatusCode, 60 | Body: string(resBody), 61 | Err: fmt.Sprintf("Page unmarshal error: %s", err), 62 | } 63 | } 64 | 65 | if loginResponse.Token == "" { 66 | return "", RequestError{ 67 | HTTPCode: response.StatusCode, 68 | Body: string(resBody), 69 | Err: "Empty token replied", 70 | } 71 | } 72 | 73 | return loginResponse.Token, nil 74 | } 75 | -------------------------------------------------------------------------------- /http-login/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | type Page struct { 15 | Name string `json:"page"` 16 | } 17 | 18 | type Response interface { 19 | GetResponse() string 20 | } 21 | 22 | type Words struct { 23 | Input string `json:"input"` 24 | Words []string `json:"words"` 25 | } 26 | 27 | func (w Words) GetResponse() string { 28 | return fmt.Sprintf("Words: %s", strings.Join(w.Words, ", ")) 29 | } 30 | 31 | type Occurrence struct { 32 | Words map[string]int `json:"words"` 33 | } 34 | 35 | func (o Occurrence) GetResponse() string { 36 | words := []string{} 37 | for word, occurrence := range o.Words { 38 | words = append(words, fmt.Sprintf("%s (%d)", word, occurrence)) 39 | } 40 | return fmt.Sprintf("Words: %s", strings.Join(words, ", ")) 41 | } 42 | 43 | func main() { 44 | var ( 45 | requestURL string 46 | password string 47 | parsedURL *url.URL 48 | err error 49 | ) 50 | flag.StringVar(&requestURL, "url", "", "url to access") 51 | flag.StringVar(&password, "password", "", "use a password to access our api") 52 | 53 | flag.Parse() 54 | 55 | if parsedURL, err = url.ParseRequestURI(requestURL); err != nil { 56 | fmt.Printf("Help: ./http-get -h\nURL is not valid URL: %s\n", requestURL) 57 | os.Exit(1) 58 | } 59 | 60 | client := http.Client{} 61 | 62 | if password != "" { 63 | token, err := doLoginRequest(client, parsedURL.Scheme+"://"+parsedURL.Host+"/login", password) 64 | if err != nil { 65 | if requestErr, ok := err.(RequestError); ok { 66 | fmt.Printf("Login failed: %s (HTTP Error: %d, Body: %s)\n", requestErr.Error(), requestErr.HTTPCode, requestErr.Body) 67 | os.Exit(1) 68 | } 69 | fmt.Printf("Login failed: %s\n", err) 70 | os.Exit(1) 71 | } 72 | client.Transport = MyJWTTransport{ 73 | transport: http.DefaultTransport, 74 | token: token, 75 | } 76 | } 77 | 78 | res, err := doRequest(client, parsedURL.String()) 79 | if err != nil { 80 | if requestErr, ok := err.(RequestError); ok { 81 | fmt.Printf("Error occurred: %s (HTTP Error: %d, Body: %s)\n", requestErr.Error(), requestErr.HTTPCode, requestErr.Body) 82 | os.Exit(1) 83 | } 84 | fmt.Printf("Error occurred: %s\n", err) 85 | os.Exit(1) 86 | } 87 | if res == nil { 88 | fmt.Printf("No response\n") 89 | os.Exit(1) 90 | } 91 | fmt.Printf("Response: %s\n", res.GetResponse()) 92 | } 93 | 94 | func doRequest(client http.Client, requestURL string) (Response, error) { 95 | 96 | response, err := client.Get(requestURL) 97 | 98 | if err != nil { 99 | return nil, fmt.Errorf("Get error: %s", err) 100 | } 101 | 102 | defer response.Body.Close() 103 | 104 | body, err := io.ReadAll(response.Body) 105 | 106 | if err != nil { 107 | return nil, fmt.Errorf("ReadAll error: %s", err) 108 | } 109 | 110 | if response.StatusCode != 200 { 111 | return nil, fmt.Errorf("Invalid output (HTTP Code %d): %s\n", response.StatusCode, string(body)) 112 | } 113 | 114 | var page Page 115 | 116 | if !json.Valid(body) { 117 | return nil, RequestError{ 118 | Err: fmt.Sprintf("Response is not a json"), 119 | HTTPCode: response.StatusCode, 120 | Body: string(body), 121 | } 122 | } 123 | 124 | err = json.Unmarshal(body, &page) 125 | if err != nil { 126 | return nil, RequestError{ 127 | Err: fmt.Sprintf("Page unmarshal error: %s", err), 128 | HTTPCode: response.StatusCode, 129 | Body: string(body), 130 | } 131 | } 132 | 133 | switch page.Name { 134 | case "words": 135 | var words Words 136 | err = json.Unmarshal(body, &words) 137 | if err != nil { 138 | return nil, fmt.Errorf("Words unmarshal error: %s", err) 139 | } 140 | 141 | return words, nil 142 | case "occurrence": 143 | var occurrence Occurrence 144 | err = json.Unmarshal(body, &occurrence) 145 | if err != nil { 146 | return nil, fmt.Errorf("Occurrence unmarshal error: %s", err) 147 | } 148 | 149 | return occurrence, nil 150 | } 151 | 152 | return nil, nil 153 | } 154 | -------------------------------------------------------------------------------- /http-login/transport.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | type MyJWTTransport struct { 6 | transport http.RoundTripper 7 | token string 8 | } 9 | 10 | func (m MyJWTTransport) RoundTrip(req *http.Request) (*http.Response, error) { 11 | if m.token != "" { 12 | req.Header.Add("Authorization", "Bearer "+m.token) 13 | } 14 | return m.transport.RoundTrip(req) 15 | } 16 | -------------------------------------------------------------------------------- /kubernetes-deploy-github/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | *.yaml 3 | -------------------------------------------------------------------------------- /kubernetes-deploy-github/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Build go project 3 | # 4 | FROM golang:1.18-alpine as go-builder 5 | 6 | WORKDIR /github-deploy 7 | 8 | COPY . . 9 | 10 | RUN apk add -u -t build-tools curl git && \ 11 | CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o github-deploy . && \ 12 | apk del build-tools && \ 13 | rm -rf /var/cache/apk/* 14 | 15 | # 16 | # Runtime container 17 | # 18 | FROM alpine:latest 19 | 20 | WORKDIR /app 21 | 22 | RUN apk --no-cache add ca-certificates bash curl 23 | 24 | COPY --from=go-builder /github-deploy/github-deploy /github-deploy 25 | 26 | ENTRYPOINT ["/github-deploy"] -------------------------------------------------------------------------------- /kubernetes-deploy-github/README.md: -------------------------------------------------------------------------------- 1 | # GitHub Deploy 2 | 3 | ## Create secret 4 | ``` 5 | kubectl create secret generic github-deploy --from-literal=webhook-secret=mysecret --from-literal=github-token=token 6 | ``` 7 | -------------------------------------------------------------------------------- /kubernetes-deploy-github/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: github-deploy 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: github-deploy 10 | template: 11 | metadata: 12 | labels: 13 | app: github-deploy 14 | spec: 15 | serviceAccountName: github-deploy 16 | containers: 17 | - name: github-deploy 18 | image: wardviaene/github-deploy 19 | ports: 20 | - name: http-port 21 | containerPort: 8080 22 | env: 23 | - name: GITHUB_TOKEN 24 | valueFrom: 25 | secretKeyRef: 26 | name: github-deploy 27 | key: github-token 28 | optional: false 29 | - name: WEBHOOK_SECRET 30 | valueFrom: 31 | secretKeyRef: 32 | name: github-deploy 33 | key: webhook-secret 34 | optional: false 35 | --- 36 | apiVersion: v1 37 | kind: Service 38 | metadata: 39 | name: github-deploy 40 | spec: 41 | ports: 42 | - port: 8080 43 | protocol: TCP 44 | selector: 45 | app: github-deploy 46 | type: LoadBalancer 47 | --- 48 | apiVersion: v1 49 | kind: ServiceAccount 50 | metadata: 51 | name: github-deploy 52 | --- 53 | apiVersion: rbac.authorization.k8s.io/v1 54 | kind: Role 55 | metadata: 56 | namespace: default 57 | name: github-deploy 58 | rules: 59 | - apiGroups: ["apps"] 60 | resources: ["deployments"] 61 | verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] 62 | --- 63 | apiVersion: rbac.authorization.k8s.io/v1 64 | kind: RoleBinding 65 | metadata: 66 | name: github-deploy 67 | namespace: default 68 | subjects: 69 | - kind: ServiceAccount 70 | name: github-deploy 71 | namespace: default 72 | roleRef: 73 | kind: Role 74 | name: github-deploy 75 | apiGroup: rbac.authorization.k8s.io 76 | -------------------------------------------------------------------------------- /kubernetes-deploy-github/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/kubernetes-deploy-github 2 | 3 | go 1.18 4 | 5 | replace github.com/golang/mock => github.com/golang/mock v1.4.4 6 | 7 | require ( 8 | github.com/PuerkitoBio/purell v1.1.1 // indirect 9 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 10 | github.com/davecgh/go-spew v1.1.1 // indirect 11 | github.com/emicklei/go-restful v2.9.5+incompatible // indirect 12 | github.com/go-logr/logr v1.2.0 // indirect 13 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 14 | github.com/go-openapi/jsonreference v0.19.5 // indirect 15 | github.com/go-openapi/swag v0.19.14 // indirect 16 | github.com/gogo/protobuf v1.3.2 // indirect 17 | github.com/golang/mock v1.5.0 // indirect 18 | github.com/golang/protobuf v1.5.2 // indirect 19 | github.com/google/gnostic v0.5.7-v3refs // indirect 20 | github.com/google/go-cmp v0.5.8 // indirect 21 | github.com/google/go-github/v45 v45.0.0 // indirect 22 | github.com/google/go-querystring v1.1.0 // indirect 23 | github.com/google/gofuzz v1.1.0 // indirect 24 | github.com/imdario/mergo v0.3.5 // indirect 25 | github.com/josharian/intern v1.0.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/mailru/easyjson v0.7.6 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 31 | github.com/spf13/pflag v1.0.5 // indirect 32 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect 33 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect 34 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 35 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect 36 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 37 | golang.org/x/text v0.3.7 // indirect 38 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 39 | google.golang.org/appengine v1.6.7 // indirect 40 | google.golang.org/protobuf v1.27.1 // indirect 41 | gopkg.in/inf.v0 v0.9.1 // indirect 42 | gopkg.in/yaml.v2 v2.4.0 // indirect 43 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 44 | k8s.io/api v0.24.1 // indirect 45 | k8s.io/apimachinery v0.24.1 // indirect 46 | k8s.io/client-go v0.24.1 // indirect 47 | k8s.io/klog/v2 v2.60.1 // indirect 48 | k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect 49 | k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect 50 | sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect 51 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect 52 | sigs.k8s.io/yaml v1.2.0 // indirect 53 | ) 54 | -------------------------------------------------------------------------------- /kubernetes-deploy-github/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "path/filepath" 9 | "time" 10 | 11 | "github.com/google/go-github/v45/github" 12 | "golang.org/x/oauth2" 13 | v1 "k8s.io/api/apps/v1" 14 | "k8s.io/apimachinery/pkg/api/errors" 15 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | "k8s.io/apimachinery/pkg/labels" 17 | "k8s.io/client-go/kubernetes" 18 | "k8s.io/client-go/kubernetes/scheme" 19 | "k8s.io/client-go/rest" 20 | "k8s.io/client-go/tools/clientcmd" 21 | "k8s.io/client-go/util/homedir" 22 | ) 23 | 24 | func main() { 25 | var ( 26 | client *kubernetes.Clientset 27 | err error 28 | ) 29 | ctx := context.Background() 30 | if client, err = getClient(ctx, false); err != nil { 31 | fmt.Printf("Error: %s\n", err) 32 | os.Exit(1) 33 | } 34 | serverInstance := server{ 35 | client: client, 36 | webhookSecretKey: os.Getenv("WEBHOOK_SECRET"), 37 | githubClient: getGitHubClient(ctx, os.Getenv("GITHUB_TOKEN")), 38 | } 39 | 40 | http.HandleFunc("/webhook", serverInstance.webhook) 41 | 42 | err = http.ListenAndServe(":8080", nil) 43 | fmt.Printf("Exited: %s\n", err) 44 | } 45 | func getClient(ctx context.Context, inCluster bool) (*kubernetes.Clientset, error) { 46 | var ( 47 | config *rest.Config 48 | err error 49 | ) 50 | if inCluster { 51 | config, err = rest.InClusterConfig() 52 | if err != nil { 53 | return nil, err 54 | } 55 | } else { 56 | kubeConfigPath := filepath.Join(homedir.HomeDir(), ".kube", "config") 57 | config, err = clientcmd.BuildConfigFromFlags("", kubeConfigPath) 58 | if err != nil { 59 | return nil, err 60 | } 61 | } 62 | client, err := kubernetes.NewForConfig(config) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return client, nil 67 | } 68 | 69 | func getGitHubClient(ctx context.Context, token string) *github.Client { 70 | if token == "" { 71 | return github.NewClient(nil) 72 | } 73 | ts := oauth2.StaticTokenSource( 74 | &oauth2.Token{AccessToken: token}, 75 | ) 76 | tc := oauth2.NewClient(ctx, ts) 77 | 78 | return github.NewClient(tc) 79 | } 80 | 81 | func deploy(ctx context.Context, client *kubernetes.Clientset, appFile []byte) (map[string]string, int32, error) { 82 | var deployment *v1.Deployment 83 | 84 | obj, groupVersionKind, err := scheme.Codecs.UniversalDeserializer().Decode(appFile, nil, nil) 85 | if err != nil { 86 | return nil, 0, fmt.Errorf("Decode error: %s", err) 87 | } 88 | 89 | switch obj.(type) { 90 | case *v1.Deployment: 91 | deployment = obj.(*v1.Deployment) 92 | default: 93 | return nil, 0, fmt.Errorf("Unrecognized type: %s\n", groupVersionKind) 94 | } 95 | 96 | _, err = client.AppsV1().Deployments("default").Get(ctx, deployment.Name, metav1.GetOptions{}) 97 | if err != nil && errors.IsNotFound(err) { 98 | deploymentResponse, err := client.AppsV1().Deployments("default").Create(ctx, deployment, metav1.CreateOptions{}) 99 | if err != nil { 100 | return nil, 0, fmt.Errorf("deployment error: %s", err) 101 | } 102 | return deploymentResponse.Spec.Template.Labels, *deploymentResponse.Spec.Replicas, nil 103 | } else if err != nil && !errors.IsNotFound(err) { 104 | return nil, 0, fmt.Errorf("deployment get error: %s", err) 105 | } 106 | 107 | deploymentResponse, err := client.AppsV1().Deployments("default").Update(ctx, deployment, metav1.UpdateOptions{}) 108 | if err != nil { 109 | return nil, 0, fmt.Errorf("deployment error: %s", err) 110 | } 111 | return deploymentResponse.Spec.Template.Labels, *deploymentResponse.Spec.Replicas, nil 112 | 113 | } 114 | func waitForPods(ctx context.Context, client *kubernetes.Clientset, deploymentLabels map[string]string, expectedPods int32) error { 115 | for { 116 | validatedLabels, err := labels.ValidatedSelectorFromSet(deploymentLabels) 117 | if err != nil { 118 | return fmt.Errorf("ValidatedSelectorFromSet error: %s", err) 119 | } 120 | 121 | podList, err := client.CoreV1().Pods("default").List(ctx, metav1.ListOptions{ 122 | LabelSelector: validatedLabels.String(), 123 | }) 124 | if err != nil { 125 | return fmt.Errorf("pod list error: %s", err) 126 | } 127 | podsRunning := 0 128 | for _, pod := range podList.Items { 129 | if pod.Status.Phase == "Running" { 130 | podsRunning++ 131 | } 132 | } 133 | 134 | fmt.Printf("Waiting for pods to become ready (running %d / %d)\n", podsRunning, len(podList.Items)) 135 | 136 | if podsRunning > 0 && podsRunning == len(podList.Items) && podsRunning == int(expectedPods) { 137 | break 138 | } 139 | 140 | time.Sleep(5 * time.Second) 141 | } 142 | return nil 143 | } 144 | -------------------------------------------------------------------------------- /kubernetes-deploy-github/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/google/go-github/v45/github" 11 | "k8s.io/client-go/kubernetes" 12 | ) 13 | 14 | type server struct { 15 | client *kubernetes.Clientset 16 | githubClient *github.Client 17 | webhookSecretKey string 18 | } 19 | 20 | func (s server) webhook(w http.ResponseWriter, req *http.Request) { 21 | ctx := context.Background() 22 | payload, err := github.ValidatePayload(req, []byte(s.webhookSecretKey)) 23 | if err != nil { 24 | w.WriteHeader(500) 25 | fmt.Printf("ValidatePayload error: %s", err) 26 | return 27 | } 28 | event, err := github.ParseWebHook(github.WebHookType(req), payload) 29 | if err != nil { 30 | w.WriteHeader(500) 31 | fmt.Printf("ValidatePayload error: %s", err) 32 | return 33 | } 34 | switch event := event.(type) { 35 | case *github.Hook: 36 | fmt.Printf("Hook is created\n") 37 | case *github.PushEvent: 38 | files := getFiles(event.Commits) 39 | fmt.Printf("Found files: %s\n", strings.Join(files, ", ")) 40 | for _, filename := range files { 41 | downloadedFile, _, err := s.githubClient.Repositories.DownloadContents(ctx, *event.Repo.Owner.Name, *event.Repo.Name, filename, &github.RepositoryContentGetOptions{}) 42 | if err != nil { 43 | w.WriteHeader(500) 44 | fmt.Printf("DownloadContents error: %s", err) 45 | return 46 | } 47 | defer downloadedFile.Close() 48 | fileBody, err := io.ReadAll(downloadedFile) 49 | if err != nil { 50 | w.WriteHeader(500) 51 | fmt.Printf("ReadAll error: %s", err) 52 | return 53 | } 54 | _, _, err = deploy(ctx, s.client, fileBody) 55 | if err != nil { 56 | w.WriteHeader(500) 57 | fmt.Printf("deploy error: %s", err) 58 | return 59 | } 60 | fmt.Printf("Deploy of %s finished\n", filename) 61 | } 62 | default: 63 | w.WriteHeader(500) 64 | fmt.Printf("Event not found: %s", event) 65 | return 66 | } 67 | } 68 | 69 | func getFiles(commits []*github.HeadCommit) []string { 70 | allFiles := []string{} 71 | for _, commit := range commits { 72 | allFiles = append(allFiles, commit.Added...) 73 | allFiles = append(allFiles, commit.Modified...) 74 | } 75 | allUniqueFiles := make(map[string]bool) 76 | for _, filename := range allFiles { 77 | allUniqueFiles[filename] = true 78 | } 79 | allUniqueFilesSlice := []string{} 80 | for filename := range allUniqueFiles { 81 | allUniqueFilesSlice = append(allUniqueFilesSlice, filename) 82 | } 83 | return allUniqueFilesSlice 84 | } 85 | -------------------------------------------------------------------------------- /kubernetes-deploy-github/server_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/google/go-github/v45/github" 8 | ) 9 | 10 | func TestGetFiles(t *testing.T) { 11 | files := getFiles([]*github.HeadCommit{ 12 | { 13 | Added: []string{"test.txt"}, 14 | }, 15 | }) 16 | if len(files) != 1 { 17 | t.Fatalf("len files is not 1. Got: %v", strings.Join(files, ",")) 18 | } 19 | if files[0] != "test.txt" { 20 | t.Fatalf("first element is not test.txt. Got: %s", files[0]) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /kubernetes-deploy/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: helloworld-deployment 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: helloworld 10 | template: 11 | metadata: 12 | labels: 13 | app: helloworld 14 | spec: 15 | containers: 16 | - name: k8s-demo 17 | image: wardviaene/k8s-demo 18 | ports: 19 | - name: nodejs-port 20 | containerPort: 3000 21 | -------------------------------------------------------------------------------- /kubernetes-deploy/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/kubernetes-deploy 2 | 3 | go 1.18 4 | 5 | replace github.com/golang/mock => github.com/golang/mock v1.4.4 6 | 7 | require ( 8 | github.com/PuerkitoBio/purell v1.1.1 // indirect 9 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 10 | github.com/davecgh/go-spew v1.1.1 // indirect 11 | github.com/emicklei/go-restful v2.9.5+incompatible // indirect 12 | github.com/go-logr/logr v1.2.0 // indirect 13 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 14 | github.com/go-openapi/jsonreference v0.19.5 // indirect 15 | github.com/go-openapi/swag v0.19.14 // indirect 16 | github.com/gogo/protobuf v1.3.2 // indirect 17 | github.com/golang/mock v1.5.0 // indirect 18 | github.com/golang/protobuf v1.5.2 // indirect 19 | github.com/google/gnostic v0.5.7-v3refs // indirect 20 | github.com/google/go-cmp v0.5.5 // indirect 21 | github.com/google/gofuzz v1.1.0 // indirect 22 | github.com/imdario/mergo v0.3.5 // indirect 23 | github.com/josharian/intern v1.0.0 // indirect 24 | github.com/json-iterator/go v1.1.12 // indirect 25 | github.com/mailru/easyjson v0.7.6 // indirect 26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 27 | github.com/modern-go/reflect2 v1.0.2 // indirect 28 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 29 | github.com/spf13/pflag v1.0.5 // indirect 30 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect 31 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect 32 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect 33 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 34 | golang.org/x/text v0.3.7 // indirect 35 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 36 | google.golang.org/appengine v1.6.7 // indirect 37 | google.golang.org/protobuf v1.27.1 // indirect 38 | gopkg.in/inf.v0 v0.9.1 // indirect 39 | gopkg.in/yaml.v2 v2.4.0 // indirect 40 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 41 | k8s.io/api v0.24.1 // indirect 42 | k8s.io/apimachinery v0.24.1 // indirect 43 | k8s.io/client-go v0.24.1 // indirect 44 | k8s.io/klog/v2 v2.60.1 // indirect 45 | k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect 46 | k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect 47 | sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect 48 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect 49 | sigs.k8s.io/yaml v1.2.0 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /kubernetes-deploy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "time" 10 | 11 | v1 "k8s.io/api/apps/v1" 12 | "k8s.io/apimachinery/pkg/api/errors" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/apimachinery/pkg/labels" 15 | "k8s.io/client-go/kubernetes" 16 | "k8s.io/client-go/kubernetes/scheme" 17 | "k8s.io/client-go/tools/clientcmd" 18 | "k8s.io/client-go/util/homedir" 19 | ) 20 | 21 | func main() { 22 | var ( 23 | client *kubernetes.Clientset 24 | deploymentLabels map[string]string 25 | err error 26 | expectedPods int32 27 | ) 28 | ctx := context.Background() 29 | if client, err = getClient(); err != nil { 30 | fmt.Printf("Error: %s", err) 31 | os.Exit(1) 32 | } 33 | if deploymentLabels, expectedPods, err = deploy(ctx, client); err != nil { 34 | fmt.Printf("Error: %s", err) 35 | os.Exit(1) 36 | } 37 | if err = waitForPods(ctx, client, deploymentLabels, expectedPods); err != nil { 38 | fmt.Printf("Error: %s", err) 39 | os.Exit(1) 40 | } 41 | fmt.Printf("deploy finished. Did a deploy with labels: %+v\n", deploymentLabels) 42 | } 43 | 44 | func getClient() (*kubernetes.Clientset, error) { 45 | // use the current context in kubeconfig 46 | config, err := clientcmd.BuildConfigFromFlags("", filepath.Join(homedir.HomeDir(), ".kube", "config")) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | // create the clientset 52 | clientset, err := kubernetes.NewForConfig(config) 53 | if err != nil { 54 | return nil, err 55 | } 56 | return clientset, nil 57 | } 58 | 59 | func deploy(ctx context.Context, client *kubernetes.Clientset) (map[string]string, int32, error) { 60 | var deployment *v1.Deployment 61 | 62 | appFile, err := ioutil.ReadFile("app.yaml") 63 | if err != nil { 64 | return nil, 0, fmt.Errorf("readfile error: %s", err) 65 | } 66 | 67 | obj, groupVersionKind, err := scheme.Codecs.UniversalDeserializer().Decode(appFile, nil, nil) 68 | if err != nil { 69 | return nil, 0, fmt.Errorf("Decode error: %s", err) 70 | } 71 | 72 | switch obj.(type) { 73 | case *v1.Deployment: 74 | deployment = obj.(*v1.Deployment) 75 | default: 76 | return nil, 0, fmt.Errorf("Unrecognized type: %s\n", groupVersionKind) 77 | } 78 | 79 | _, err = client.AppsV1().Deployments("default").Get(ctx, deployment.Name, metav1.GetOptions{}) 80 | if err != nil && errors.IsNotFound(err) { 81 | deploymentResponse, err := client.AppsV1().Deployments("default").Create(ctx, deployment, metav1.CreateOptions{}) 82 | if err != nil { 83 | return nil, 0, fmt.Errorf("deployment error: %s", err) 84 | } 85 | return deploymentResponse.Spec.Template.Labels, *deploymentResponse.Spec.Replicas, nil 86 | } else if err != nil && !errors.IsNotFound(err) { 87 | return nil, 0, fmt.Errorf("deployment get error: %s", err) 88 | } 89 | 90 | deploymentResponse, err := client.AppsV1().Deployments("default").Update(ctx, deployment, metav1.UpdateOptions{}) 91 | if err != nil { 92 | return nil, 0, fmt.Errorf("deployment error: %s", err) 93 | } 94 | return deploymentResponse.Spec.Template.Labels, *deploymentResponse.Spec.Replicas, nil 95 | 96 | } 97 | func waitForPods(ctx context.Context, client *kubernetes.Clientset, deploymentLabels map[string]string, expectedPods int32) error { 98 | for { 99 | validatedLabels, err := labels.ValidatedSelectorFromSet(deploymentLabels) 100 | if err != nil { 101 | return fmt.Errorf("ValidatedSelectorFromSet error: %s", err) 102 | } 103 | 104 | podList, err := client.CoreV1().Pods("default").List(ctx, metav1.ListOptions{ 105 | LabelSelector: validatedLabels.String(), 106 | }) 107 | if err != nil { 108 | return fmt.Errorf("pod list error: %s", err) 109 | } 110 | podsRunning := 0 111 | for _, pod := range podList.Items { 112 | if pod.Status.Phase == "Running" { 113 | podsRunning++ 114 | } 115 | } 116 | 117 | fmt.Printf("Waiting for pods to become ready (running %d / %d)\n", podsRunning, len(podList.Items)) 118 | 119 | if podsRunning > 0 && podsRunning == len(podList.Items) && podsRunning == int(expectedPods) { 120 | break 121 | } 122 | 123 | time.Sleep(5 * time.Second) 124 | } 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /mutex-demo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/mutex-demo 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /mutex-demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type mytype struct { 11 | counter int 12 | mu sync.Mutex 13 | } 14 | 15 | func main() { 16 | mytypeInstance := mytype{} 17 | finished := make(chan bool) 18 | for i := 0; i < 10; i++ { 19 | go func(mytypeInstance *mytype) { 20 | mytypeInstance.mu.Lock() 21 | fmt.Printf("input counter: %d\n", mytypeInstance.counter) 22 | mytypeInstance.counter++ 23 | time.Sleep(time.Duration(rand.Intn(5)) * time.Second) 24 | if mytypeInstance.counter == 5 { 25 | fmt.Printf("Found counter == 5\n") 26 | } 27 | fmt.Printf("output counter: %d\n", mytypeInstance.counter) 28 | finished <- true 29 | mytypeInstance.mu.Unlock() 30 | }(&mytypeInstance) 31 | } 32 | for i := 0; i < 10; i++ { 33 | <-finished 34 | } 35 | fmt.Printf("Counter: %d\n", mytypeInstance.counter) 36 | } 37 | -------------------------------------------------------------------------------- /oidc-demo/.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | -------------------------------------------------------------------------------- /oidc-demo/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Build go project 3 | # 4 | FROM golang:1.18-alpine as go-builder 5 | 6 | WORKDIR /oidc-demo 7 | 8 | COPY . . 9 | 10 | RUN apk add -u -t build-tools curl git && \ 11 | CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server cmd/server/*.go && \ 12 | apk del build-tools && \ 13 | rm -rf /var/cache/apk/* 14 | 15 | # 16 | # Runtime container 17 | # 18 | FROM alpine:latest 19 | 20 | WORKDIR /app 21 | 22 | RUN apk --no-cache add ca-certificates bash curl 23 | 24 | COPY --from=go-builder /oidc-demo/server /app/oidc-demo-server 25 | 26 | COPY config.yaml /app/config.yaml 27 | 28 | ENTRYPOINT ["/app/oidc-demo-server"] 29 | -------------------------------------------------------------------------------- /oidc-demo/cmd/appserver/jwt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rsa" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "math/big" 10 | "net/http" 11 | "net/url" 12 | 13 | "github.com/golang-jwt/jwt/v4" 14 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/oidc" 15 | ) 16 | 17 | // gets token from tokenUrl validating token with jwksUrl and returning token & claims 18 | func getTokenFromCode(tokenUrl, jwksUrl, redirectUri, clientID, clientSecret, code string) ([]*jwt.Token, *jwt.RegisteredClaims, error) { 19 | 20 | values := url.Values{} 21 | values.Add("grant_type", "authorization_code") 22 | values.Add("client_id", clientID) 23 | values.Add("client_secret", clientSecret) 24 | values.Add("redirect_uri", redirectUri) 25 | values.Add("code", code) 26 | 27 | res, err := http.PostForm(tokenUrl, values) 28 | if err != nil { 29 | return nil, nil, err 30 | } 31 | defer res.Body.Close() 32 | 33 | body, err := io.ReadAll(res.Body) 34 | if err != nil { 35 | return nil, nil, err 36 | } 37 | 38 | if res.StatusCode != 200 { 39 | return nil, nil, fmt.Errorf("Statuscode was not 200.") 40 | } 41 | 42 | var token oidc.Token 43 | 44 | err = json.Unmarshal(body, &token) 45 | if err != nil { 46 | return nil, nil, fmt.Errorf("Unmarshal token error: %s", err) 47 | } 48 | 49 | claims := &jwt.RegisteredClaims{} 50 | parsedIDToken, err := jwt.ParseWithClaims(token.IDToken, claims, func(token *jwt.Token) (interface{}, error) { 51 | kid, ok := token.Header["kid"] 52 | if !ok { 53 | return nil, fmt.Errorf("kid not found") 54 | } 55 | publicKey, err := getPublicKeyFromJwks(jwksUrl, kid.(string)) 56 | if err != nil { 57 | return nil, fmt.Errorf("getPublicKeyFromJwks error: %s", err) 58 | } 59 | return publicKey, nil 60 | }) 61 | 62 | if err != nil { 63 | return nil, nil, fmt.Errorf("Token parsing failed: %s", err) 64 | } 65 | 66 | AccessTokenClaims := &jwt.RegisteredClaims{} 67 | parsedAccessToken, err := jwt.ParseWithClaims(token.AccessToken, AccessTokenClaims, func(token *jwt.Token) (interface{}, error) { 68 | kid, ok := token.Header["kid"] 69 | if !ok { 70 | return nil, fmt.Errorf("kid not found") 71 | } 72 | publicKey, err := getPublicKeyFromJwks(jwksUrl, kid.(string)) 73 | if err != nil { 74 | return nil, fmt.Errorf("getPublicKeyFromJwks error: %s", err) 75 | } 76 | return publicKey, nil 77 | }) 78 | 79 | if err != nil { 80 | return nil, nil, fmt.Errorf("Token parsing failed: %s", err) 81 | } 82 | 83 | return []*jwt.Token{parsedIDToken, parsedAccessToken}, claims, nil 84 | } 85 | func getPublicKeyFromJwks(jwksUrl string, kid string) (*rsa.PublicKey, error) { 86 | res, err := http.Get(jwksUrl) 87 | if err != nil { 88 | return nil, err 89 | } 90 | defer res.Body.Close() 91 | body, err := io.ReadAll(res.Body) 92 | if err != nil { 93 | return nil, err 94 | } 95 | if res.StatusCode != 200 { 96 | return nil, fmt.Errorf("invalid statusCode: %d", res.StatusCode) 97 | } 98 | 99 | // parse jwks 100 | var jwks oidc.Jwks 101 | err = json.Unmarshal(body, &jwks) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | for _, jwksKeyEntry := range jwks.Keys { 107 | if jwksKeyEntry.Kid == kid { 108 | nBytes, err := base64.StdEncoding.DecodeString(jwksKeyEntry.N) 109 | if err != nil { 110 | return nil, fmt.Errorf("decodestring error: %s", err) 111 | } 112 | n := big.NewInt(0) 113 | n.SetBytes(nBytes) 114 | return &rsa.PublicKey{ 115 | N: n, 116 | E: 65537, 117 | }, nil 118 | } 119 | } 120 | return nil, fmt.Errorf("No public key found with kid %s", kid) 121 | } 122 | -------------------------------------------------------------------------------- /oidc-demo/cmd/appserver/jwt_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | "net/http" 10 | "net/http/httptest" 11 | "testing" 12 | "time" 13 | 14 | "github.com/golang-jwt/jwt/v4" 15 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/oidc" 16 | ) 17 | 18 | func TestGetTokenFromCode(t *testing.T) { 19 | // generate private key 20 | privateKey, err := rsa.GenerateKey(rand.Reader, 4096) 21 | if err != nil { 22 | t.Fatalf("Couldn't generate rsa key") 23 | } 24 | 25 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 | // generate jwt and return 27 | if r.URL.Path == "/token" { 28 | claims := jwt.MapClaims{ 29 | "iss": "http://example.com", 30 | "sub": "1-2-3-5", 31 | "aud": "1-2-3-4", 32 | "exp": time.Now().Add(1 * time.Hour).Unix(), 33 | "iat": time.Now().Unix(), 34 | } 35 | token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) 36 | token.Header["kid"] = "0-0-0-1" 37 | 38 | idTokenString, err := token.SignedString(privateKey) 39 | if err != nil { 40 | t.Fatalf("SignedString: %s", err) 41 | } 42 | // generate Access Token 43 | claims = jwt.MapClaims{ 44 | "iss": "http://example.com", 45 | "sub": "1-2-3-4", 46 | "aud": []string{ 47 | "http://example.com/userinfo", 48 | }, 49 | "exp": time.Now().Add(1 * time.Hour).Unix(), 50 | "iat": time.Now().Unix(), 51 | } 52 | token = jwt.NewWithClaims(jwt.SigningMethodRS256, claims) 53 | token.Header["kid"] = "0-0-0-1" 54 | 55 | accessTokenString, err := token.SignedString(privateKey) 56 | if err != nil { 57 | t.Fatalf("SignedString: %s", err) 58 | } 59 | 60 | refreshToken, err := oidc.GetRandomString(64) 61 | if err != nil { 62 | t.Fatalf("random string error: %s", err) 63 | } 64 | responseToken := oidc.Token{ 65 | IDToken: idTokenString, 66 | AccessToken: accessTokenString, 67 | ExpiresIn: 60, 68 | TokenType: "bearer", 69 | RefreshToken: refreshToken, 70 | } 71 | 72 | out, err := json.Marshal(responseToken) 73 | if err != nil { 74 | t.Fatalf("json marshal error: %s", err) 75 | return 76 | } 77 | w.Write(out) 78 | } else if r.URL.Path == "/jwks.json" { 79 | jwks := oidc.Jwks{ 80 | Keys: []oidc.JwksKey{ 81 | { 82 | N: base64.StdEncoding.EncodeToString(privateKey.PublicKey.N.Bytes()), 83 | E: "AQAB", 84 | Alg: "RS256", 85 | Use: "sig", 86 | Kid: "0-0-0-1", 87 | Kty: "RSA", 88 | }, 89 | }, 90 | } 91 | out, err := json.Marshal(jwks) 92 | if err != nil { 93 | t.Fatalf("jwks marshall error: %s", err) 94 | return 95 | } 96 | w.Write(out) 97 | } 98 | })) 99 | defer ts.Close() 100 | 101 | _, claims, err := getTokenFromCode(ts.URL+"/token", ts.URL+"/jwks.json", "http://localhost:8081", "1-2-3-4", "secret", "mycode") 102 | if err != nil { 103 | t.Fatalf("getTokenFromCode error: %s", err) 104 | } 105 | if claims == nil { 106 | t.Fatalf("claims is nil") 107 | } 108 | 109 | fmt.Printf("Exchanged code into token. Subject: %s\n", claims.Subject) 110 | 111 | } 112 | -------------------------------------------------------------------------------- /oidc-demo/cmd/appserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/oidc" 10 | ) 11 | 12 | const redirectUri = "http://localhost:8081/callback" 13 | 14 | type app struct { 15 | states map[string]bool 16 | } 17 | 18 | func main() { 19 | 20 | a := app{ 21 | states: make(map[string]bool), 22 | } 23 | 24 | http.HandleFunc("/", a.index) 25 | http.HandleFunc("/callback", a.callback) 26 | 27 | err := http.ListenAndServe(":8081", nil) 28 | if err != nil { 29 | fmt.Printf("ListenAndServe error: %s\n", err) 30 | } 31 | } 32 | 33 | func (a *app) index(w http.ResponseWriter, r *http.Request) { 34 | oidcEndpoint := os.Getenv("OIDC_ENDPOINT") 35 | discovery, err := oidc.ParseDiscovery(oidcEndpoint + "/.well-known/openid-configuration") 36 | if err != nil { 37 | returnError(w, fmt.Errorf("ParseDiscovery error: %s", err)) 38 | return 39 | } 40 | 41 | state, err := oidc.GetRandomString(64) 42 | if err != nil { 43 | returnError(w, fmt.Errorf("GetRandomString error: %s", err)) 44 | return 45 | } 46 | 47 | a.states[state] = true 48 | 49 | authorizationURL := fmt.Sprintf("%s?client_id=%s&redirect_uri=%s&scope=openid&response_type=code&state=%s", discovery.AuthorizationEndpoint, os.Getenv("CLIENT_ID"), redirectUri, state) 50 | w.Write([]byte(` 51 | 52 | 53 | 54 | `)) 55 | } 56 | 57 | func (a *app) callback(w http.ResponseWriter, r *http.Request) { 58 | oidcEndpoint := os.Getenv("OIDC_ENDPOINT") 59 | discovery, err := oidc.ParseDiscovery(oidcEndpoint + "/.well-known/openid-configuration") 60 | if err != nil { 61 | returnError(w, fmt.Errorf("ParseDiscovery error: %s", err)) 62 | return 63 | } 64 | 65 | if _, ok := a.states[r.URL.Query().Get("state")]; !ok { 66 | returnError(w, fmt.Errorf("state mismatch error")) 67 | return 68 | } 69 | 70 | delete(a.states, r.URL.Query().Get("state")) 71 | 72 | tokens, _, err := getTokenFromCode(discovery.TokenEndpoint, discovery.JwksURI, redirectUri, os.Getenv("CLIENT_ID"), os.Getenv("CLIENT_SECRET"), r.URL.Query().Get("code")) 73 | if err != nil { 74 | returnError(w, fmt.Errorf("getTokenFromCode error: %s", err)) 75 | return 76 | } 77 | 78 | req, err := http.NewRequest("GET", discovery.UserinfoEndpoint, nil) 79 | if err != nil { 80 | returnError(w, fmt.Errorf("newRequest error: %s", err)) 81 | return 82 | } 83 | req.Header.Add("Authorization", "Bearer "+tokens[1].Raw) 84 | 85 | client := &http.Client{} 86 | 87 | res, err := client.Do(req) 88 | if err != nil { 89 | returnError(w, fmt.Errorf("do request error: %s", err)) 90 | return 91 | } 92 | defer res.Body.Close() 93 | body, err := io.ReadAll(res.Body) 94 | if err != nil { 95 | returnError(w, fmt.Errorf("ReadAll error: %s", err)) 96 | return 97 | } 98 | 99 | fmt.Printf("IDToken: %s\n", tokens[0].Raw) 100 | 101 | w.Write([]byte(fmt.Sprintf("Token received. Userinfo: %s", body))) 102 | } 103 | 104 | func returnError(w http.ResponseWriter, err error) { 105 | w.WriteHeader(http.StatusInternalServerError) 106 | w.Write([]byte(err.Error())) 107 | fmt.Printf("Error: %s\n", err) 108 | } 109 | -------------------------------------------------------------------------------- /oidc-demo/cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/server" 12 | "github.com/wardviaene/golang-for-devops-course/ssh-demo" 13 | ) 14 | 15 | const configFile = "config.yaml" 16 | 17 | func main() { 18 | var ( 19 | privateKey []byte 20 | err error 21 | ) 22 | // read config 23 | if _, err = os.Stat(configFile); errors.Is(err, os.ErrNotExist) { 24 | fmt.Printf("Error: %s doesn't exist\n", configFile) 25 | os.Exit(1) 26 | } 27 | config, err := ioutil.ReadFile(configFile) 28 | if err != nil { 29 | log.Fatalf("Failed to load %s, err: %v", configFile, err) 30 | } 31 | // read encryption key 32 | if _, err = os.Stat("enckey.pem"); errors.Is(err, os.ErrNotExist) { 33 | if privateKey, _, err = ssh.GenerateKeys(); err != nil { 34 | fmt.Printf("Error: %s\n", err) 35 | os.Exit(1) 36 | } 37 | if err = os.WriteFile("enckey.pem", privateKey, 0600); err != nil { 38 | fmt.Printf("Error: %s\n", err) 39 | os.Exit(1) 40 | } 41 | } else { 42 | privateKey, err = ioutil.ReadFile("enckey.pem") 43 | if err != nil { 44 | log.Fatalf("Failed to load authorized_keys, err: %v", err) 45 | } 46 | 47 | } 48 | fmt.Printf("Server stopped: %s", server.Start(&http.Server{Addr: ":8080"}, privateKey, server.ReadConfig(config))) 49 | } 50 | -------------------------------------------------------------------------------- /oidc-demo/config.yaml: -------------------------------------------------------------------------------- 1 | url: "http://localhost:8080" 2 | apps: 3 | app1: 4 | clientID: "1-2-3-4" 5 | clientSecret: "secret" 6 | issuer: "http://localhost:8080" 7 | redirectURIs: ["http://localhost:8081/callback"] -------------------------------------------------------------------------------- /oidc-demo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/oidc-demo 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/golang-jwt/jwt/v4 v4.4.2 7 | github.com/wardviaene/golang-for-devops-course/ssh-demo v0.0.0-20220616215025-d61a2b0cee5f 8 | gopkg.in/yaml.v3 v3.0.1 9 | ) 10 | 11 | require ( 12 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect 13 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect 14 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /oidc-demo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= 2 | github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 3 | github.com/wardviaene/golang-for-devops-course/ssh-demo v0.0.0-20220616215025-d61a2b0cee5f h1:hsbzhLUNJIwe7Kj45QVOvvVvqqMZM42NQVBtiQVWVe8= 4 | github.com/wardviaene/golang-for-devops-course/ssh-demo v0.0.0-20220616215025-d61a2b0cee5f/go.mod h1:nMRrSdJ6buN2/nVCX4zAr1VTIzeZNqK7SgTepO1zRdA= 5 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= 6 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 7 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= 8 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 9 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= 10 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 14 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 15 | -------------------------------------------------------------------------------- /oidc-demo/pkg/oidc/discovery.go: -------------------------------------------------------------------------------- 1 | package oidc 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | func ParseDiscovery(url string) (Discovery, error) { 10 | var discovery Discovery 11 | res, err := http.Get(url) 12 | if err != nil { 13 | return discovery, err 14 | } 15 | defer res.Body.Close() 16 | 17 | body, err := io.ReadAll(res.Body) 18 | 19 | if err = json.Unmarshal(body, &discovery); err != nil { 20 | return discovery, err 21 | } 22 | return discovery, nil 23 | } 24 | -------------------------------------------------------------------------------- /oidc-demo/pkg/oidc/rand.go: -------------------------------------------------------------------------------- 1 | package oidc 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | "strings" 9 | ) 10 | 11 | func GetRandomString(n int) (string, error) { 12 | buf := make([]byte, n) 13 | 14 | _, err := io.ReadFull(rand.Reader, buf) 15 | if err != nil { 16 | return "", fmt.Errorf("crypto/rand Reader error: %s", err) 17 | } 18 | 19 | randomStr := base64.URLEncoding.EncodeToString(buf) 20 | randomStr = strings.Replace(randomStr, "=", "", -1) 21 | 22 | return randomStr, nil 23 | } 24 | -------------------------------------------------------------------------------- /oidc-demo/pkg/oidc/types.go: -------------------------------------------------------------------------------- 1 | package oidc 2 | 3 | type Discovery struct { 4 | Issuer string `json:"issuer"` 5 | AuthorizationEndpoint string `json:"authorization_endpoint"` 6 | TokenEndpoint string `json:"token_endpoint"` 7 | UserinfoEndpoint string `json:"userinfo_endpoint"` 8 | JwksURI string `json:"jwks_uri"` 9 | ScopesSupported []string `json:"scopes_supported"` 10 | ResponseTypesSupported []string `json:"response_types_supported"` 11 | TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"` 12 | IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"` 13 | ClaimsSupported []string `json:"claims_supported"` 14 | SubjectTypesSupported []string `json:"subject_types_supported"` 15 | } 16 | 17 | type Token struct { 18 | AccessToken string `json:"access_token"` 19 | TokenType string `json:"token_type"` 20 | RefreshToken string `json:"refresh_token"` 21 | ExpiresIn int `json:"expires_in"` 22 | IDToken string `json:"id_token"` 23 | } 24 | 25 | // jwks 26 | type Jwks struct { 27 | Keys []JwksKey `json:"keys"` 28 | } 29 | type JwksKey struct { 30 | N string `json:"n"` 31 | E string `json:"e"` 32 | Alg string `json:"alg"` 33 | Use string `json:"use"` 34 | Kid string `json:"kid"` 35 | Kty string `json:"kty"` 36 | } 37 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/authorization.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/oidc" 8 | ) 9 | 10 | func (s *server) authorization(w http.ResponseWriter, r *http.Request) { 11 | var ( 12 | clientID string 13 | redirectURI string 14 | scope string 15 | responseType string 16 | state string 17 | ) 18 | if clientID = r.URL.Query().Get("client_id"); clientID == "" { 19 | returnError(w, fmt.Errorf("client_id is empty")) 20 | return 21 | } 22 | if redirectURI = r.URL.Query().Get("redirect_uri"); redirectURI == "" { 23 | returnError(w, fmt.Errorf("redirectURI is empty")) 24 | return 25 | } 26 | if scope = r.URL.Query().Get("scope"); scope == "" { 27 | returnError(w, fmt.Errorf("scope is empty")) 28 | return 29 | } 30 | if responseType = r.URL.Query().Get("response_type"); responseType != "code" { 31 | returnError(w, fmt.Errorf("response_type is empty")) 32 | return 33 | } 34 | if state = r.URL.Query().Get("state"); state == "" { 35 | returnError(w, fmt.Errorf("state is empty")) 36 | return 37 | } 38 | appConfig := AppConfig{} 39 | for _, app := range s.Config.Apps { 40 | if app.ClientID == clientID { 41 | appConfig = app 42 | } 43 | } 44 | if appConfig.ClientID == "" { 45 | returnError(w, fmt.Errorf("client_id not found")) 46 | return 47 | } 48 | 49 | found := false 50 | for _, redirectURIConfig := range appConfig.RedirectURIs { 51 | if redirectURIConfig == redirectURI { 52 | found = true 53 | } 54 | } 55 | if !found { 56 | returnError(w, fmt.Errorf("redirect_uri not whitelisted")) 57 | return 58 | } 59 | 60 | sessionID, err := oidc.GetRandomString(128) 61 | if err != nil { 62 | returnError(w, fmt.Errorf("GetRandomString error: %s", err)) 63 | return 64 | } 65 | 66 | s.LoginRequest[sessionID] = LoginRequest{ 67 | ClientID: clientID, 68 | RedirectURI: redirectURI, 69 | Scope: scope, 70 | ResponseType: responseType, 71 | State: state, 72 | AppConfig: appConfig, 73 | } 74 | 75 | w.Header().Add("location", fmt.Sprintf("/login?sessionID=%s", sessionID)) 76 | w.WriteHeader(http.StatusFound) 77 | 78 | } 79 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/authorization_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestAuthorization(t *testing.T) { 12 | s := newServer(privkeyPem, testConfig) // testConfig is defined in http_test.go and defines a static config 13 | 14 | endpoint := fmt.Sprintf("/authorization?client_id=%s&redirect_uri=%s&scope=openid&response_type=code&state=randomstring", 15 | s.Config.Apps["app1"].ClientID, // app1 is defined in testConfig 16 | s.Config.Apps["app1"].RedirectURIs[0], 17 | ) 18 | req := httptest.NewRequest(http.MethodGet, endpoint, nil) 19 | w := httptest.NewRecorder() 20 | s.authorization(w, req) 21 | res := w.Result() 22 | defer res.Body.Close() 23 | _, err := io.ReadAll(res.Body) 24 | if err != nil { 25 | t.Errorf("Readall error: %s", err) 26 | } 27 | 28 | if res.Header.Get("location") == "" { 29 | t.Fatalf("Location header not set") 30 | } 31 | 32 | if res.StatusCode != http.StatusFound { 33 | t.Fatalf("HTTP StatusCode: %d (expected %d)", res.StatusCode, http.StatusFound) 34 | } 35 | 36 | fmt.Printf("Got location: %s\n", res.Header.Get("location")) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/config.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "gopkg.in/yaml.v3" 4 | 5 | func ReadConfig(bytes []byte) Config { 6 | var config Config 7 | 8 | // config parsing 9 | err := yaml.Unmarshal(bytes, &config) 10 | if err != nil { 11 | config.LoadError = err 12 | } 13 | return config 14 | } 15 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/discovery.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/oidc" 8 | ) 9 | 10 | func (s *server) discovery(w http.ResponseWriter, r *http.Request) { 11 | discovery := oidc.Discovery{ 12 | Issuer: s.Config.Url, 13 | AuthorizationEndpoint: s.Config.Url + "/authorization", 14 | TokenEndpoint: s.Config.Url + "/token", 15 | UserinfoEndpoint: s.Config.Url + "/userinfo", 16 | JwksURI: s.Config.Url + "/jwks.json", 17 | ScopesSupported: []string{"openid"}, // was oidc in lecture, but should be openid 18 | ResponseTypesSupported: []string{"code"}, 19 | TokenEndpointAuthMethodsSupported: []string{"none"}, 20 | IDTokenSigningAlgValuesSupported: []string{"RS256"}, 21 | ClaimsSupported: []string{"iss", "sub", "aud", "exp", "nbf", "iat"}, 22 | SubjectTypesSupported: []string{"public"}, 23 | } 24 | out, err := json.Marshal(discovery) 25 | if err != nil { 26 | returnError(w, err) 27 | return 28 | } 29 | w.Write(out) 30 | } 31 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/http.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type server struct { 9 | PrivateKey []byte 10 | Config Config 11 | LoginRequest map[string]LoginRequest 12 | Codes map[string]LoginRequest 13 | } 14 | 15 | func newServer(privateKey []byte, config Config) *server { 16 | return &server{ 17 | PrivateKey: privateKey, 18 | Config: config, 19 | LoginRequest: make(map[string]LoginRequest), 20 | Codes: make(map[string]LoginRequest), 21 | } 22 | } 23 | 24 | func Start(httpServer *http.Server, privateKey []byte, config Config) error { 25 | s := newServer(privateKey, config) 26 | 27 | http.HandleFunc("/authorization", s.authorization) 28 | http.HandleFunc("/token", s.token) 29 | http.HandleFunc("/login", s.login) 30 | http.HandleFunc("/jwks.json", s.jwks) 31 | http.HandleFunc("/.well-known/openid-configuration", s.discovery) 32 | http.HandleFunc("/userinfo", s.userinfo) 33 | 34 | return httpServer.ListenAndServe() 35 | } 36 | 37 | func returnError(w http.ResponseWriter, err error) { 38 | w.WriteHeader(http.StatusBadRequest) 39 | w.Write([]byte(err.Error())) 40 | fmt.Printf("Error: %s\n", err) 41 | } 42 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/http_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "log" 10 | "net/http" 11 | "os" 12 | "strings" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | /* Global code for all tests */ 18 | var privkeyPem []byte 19 | 20 | var testConfig Config 21 | 22 | func TestMain(m *testing.M) { 23 | err := testSetup() 24 | if err != nil { 25 | log.Fatalf("test setup failed: %s", err) 26 | } 27 | code := m.Run() 28 | os.Exit(code) 29 | } 30 | 31 | func testSetup() error { 32 | privKey, err := rsa.GenerateKey(rand.Reader, 4096) 33 | if err != nil { 34 | return err 35 | } 36 | privkeyBytes := x509.MarshalPKCS1PrivateKey(privKey) 37 | privkeyPem = pem.EncodeToMemory( 38 | &pem.Block{ 39 | Type: "RSA PRIVATE KEY", 40 | Bytes: privkeyBytes, 41 | }, 42 | ) 43 | // populate testConfig 44 | testConfig = Config{ 45 | Apps: map[string]AppConfig{ 46 | "app1": { 47 | ClientID: "1-2-3-4", 48 | ClientSecret: "secret", 49 | Issuer: "http://localhost:8080", 50 | RedirectURIs: []string{"http://localhost:8081/callback"}, 51 | }, 52 | }, 53 | } 54 | return nil 55 | } 56 | 57 | func TestStart(t *testing.T) { 58 | httpServer := &http.Server{Addr: ":8080"} 59 | 60 | go func() { 61 | err := Start(httpServer, privkeyPem, testConfig) 62 | if err != nil && err.Error() != "http: Server closed" { 63 | t.Errorf("Start error: %s\n", err) 64 | } 65 | }() 66 | 67 | time.Sleep(1 * time.Second) // give time for the http server to start 68 | 69 | endpoints := []string{"/authorization", "token", "login", "jwks.json", "/.well-known/openid-configuration", "userinfo"} 70 | for _, endpoint := range endpoints { 71 | addr := httpServer.Addr 72 | if strings.HasPrefix(addr, ":") { 73 | addr = "http://localhost" + addr 74 | } else { 75 | addr = "http://" + addr 76 | } 77 | res, err := http.Get(addr + "/" + endpoint) 78 | if err != nil { 79 | t.Fatalf("http get error: %s", err) 80 | } 81 | if res.StatusCode == 404 || res.StatusCode >= 500 { 82 | t.Errorf("Endpoint %s not available. Statuscode: %d", endpoint, res.StatusCode) 83 | } 84 | } 85 | 86 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 87 | defer cancel() 88 | if err := httpServer.Shutdown(ctx); err != nil { 89 | t.Fatalf("Could not shut down http server") 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/jwks.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | 9 | "github.com/golang-jwt/jwt/v4" 10 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/oidc" 11 | ) 12 | 13 | func (s *server) jwks(w http.ResponseWriter, r *http.Request) { 14 | 15 | privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(s.PrivateKey) 16 | if err != nil { 17 | returnError(w, fmt.Errorf("private key parsing error: %s", err)) 18 | return 19 | } 20 | 21 | publicKey := privateKey.PublicKey 22 | 23 | jwks := oidc.Jwks{ 24 | Keys: []oidc.JwksKey{ 25 | { 26 | Kid: "0-0-0-1", 27 | Alg: "RS256", 28 | Kty: "RSA", 29 | Use: "sig", 30 | N: base64.StdEncoding.EncodeToString(publicKey.N.Bytes()), 31 | E: "AQAB", 32 | }, 33 | }, 34 | } 35 | out, err := json.Marshal(jwks) 36 | if err != nil { 37 | returnError(w, fmt.Errorf("jwks marshal error: %s", err)) 38 | return 39 | } 40 | w.Write(out) 41 | } 42 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/login.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/oidc" 12 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/users" 13 | ) 14 | 15 | //go:embed templates/* 16 | var templateFs embed.FS 17 | 18 | func (s *server) login(w http.ResponseWriter, r *http.Request) { 19 | if r.Method == "POST" { 20 | err := r.ParseForm() 21 | if err != nil { 22 | returnError(w, fmt.Errorf("Parseform error: %s", err)) 23 | return 24 | } 25 | sessionID := r.PostForm.Get("sessionID") 26 | loginRequest, ok := s.LoginRequest[sessionID] 27 | if !ok { 28 | returnError(w, fmt.Errorf("Session not found")) 29 | return 30 | } 31 | 32 | auth, user, err := users.Auth(r.PostForm.Get("login"), r.PostForm.Get("password"), "") 33 | if err != nil { 34 | returnError(w, fmt.Errorf("Authentication error: %s", err)) 35 | return 36 | } 37 | 38 | if auth { 39 | code, err := oidc.GetRandomString(64) 40 | if err != nil { 41 | returnError(w, fmt.Errorf("GetRandomString error: %s", err)) 42 | return 43 | } 44 | 45 | loginRequest.CodeIssuedAt = time.Now() 46 | loginRequest.User = user 47 | s.Codes[code] = loginRequest 48 | 49 | delete(s.LoginRequest, sessionID) 50 | 51 | w.Header().Add("location", fmt.Sprintf("%s?code=%s&state=%s", loginRequest.RedirectURI, code, loginRequest.State)) 52 | w.WriteHeader(http.StatusFound) 53 | } else { 54 | w.WriteHeader(http.StatusUnauthorized) 55 | w.Write([]byte("Authentication failed")) 56 | } 57 | 58 | return 59 | } 60 | var ( 61 | sessionID string 62 | ) 63 | if sessionID = r.URL.Query().Get("sessionID"); sessionID == "" { 64 | returnError(w, fmt.Errorf("sessionID is empty")) 65 | return 66 | } 67 | 68 | templateFile, err := templateFs.Open("templates/login.html") 69 | if err != nil { 70 | returnError(w, fmt.Errorf("templateFS open error: %s", err)) 71 | return 72 | } 73 | templateFileBytes, err := io.ReadAll(templateFile) 74 | if err != nil { 75 | returnError(w, fmt.Errorf("ReadAll error: %s", err)) 76 | return 77 | } 78 | 79 | templateFileStr := strings.Replace(string(templateFileBytes), "$SESSIONID", sessionID, -1) 80 | 81 | w.Write([]byte(templateFileStr)) 82 | } 83 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/login_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/http/httptest" 9 | "net/url" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func TestLoginGet(t *testing.T) { 15 | s := newServer(privkeyPem, testConfig) 16 | 17 | // 1. authorization flow 18 | endpoint := fmt.Sprintf("/authorization?client_id=%s&redirect_uri=%s&scope=openid&response_type=code&state=randomstring", 19 | s.Config.Apps["app1"].ClientID, 20 | s.Config.Apps["app1"].RedirectURIs[0], 21 | ) 22 | req := httptest.NewRequest(http.MethodGet, endpoint, nil) 23 | w := httptest.NewRecorder() 24 | s.authorization(w, req) 25 | res := w.Result() 26 | defer res.Body.Close() 27 | _, err := io.ReadAll(res.Body) 28 | if err != nil { 29 | t.Errorf("Readall error: %s", err) 30 | } 31 | 32 | if res.Header.Get("location") == "" { 33 | t.Fatalf("Location header not set") 34 | } 35 | 36 | // 2. login flow 37 | req = httptest.NewRequest(http.MethodGet, res.Header.Get("location"), nil) 38 | w = httptest.NewRecorder() 39 | s.login(w, req) 40 | loginRes := w.Result() 41 | defer loginRes.Body.Close() 42 | body, err := io.ReadAll(loginRes.Body) 43 | if err != nil { 44 | t.Errorf("Readall error: %s", err) 45 | } 46 | if loginRes.StatusCode != http.StatusOK { 47 | t.Fatalf("HTTP StatusCode: %d (expected %d)", res.StatusCode, http.StatusAccepted) 48 | } 49 | 50 | if !strings.Contains(strings.ToLower(string(body)), " 2 | 3 | 4 | 5 | 6 | 7 | Login example 8 | 9 | 10 | 11 |
12 |
13 |
14 |

Login form

15 |

Login form example from templates/login.html

16 | 17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 |
26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/token.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/golang-jwt/jwt/v4" 10 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/oidc" 11 | ) 12 | 13 | func (s *server) token(w http.ResponseWriter, r *http.Request) { 14 | if r.Method != "POST" { 15 | returnError(w, fmt.Errorf("Not a POST request")) 16 | return 17 | } 18 | if err := r.ParseForm(); err != nil { 19 | returnError(w, fmt.Errorf("ParseForm error: %s", err)) 20 | return 21 | } 22 | if r.PostForm.Get("grant_type") != "authorization_code" { 23 | returnError(w, fmt.Errorf("invalid grant type: %s", r.PostForm.Get("grant_type"))) 24 | return 25 | } 26 | loginRequest, ok := s.Codes[r.PostForm.Get("code")] 27 | if !ok { 28 | returnError(w, fmt.Errorf("invalid code")) 29 | return 30 | } 31 | if time.Now().After(loginRequest.CodeIssuedAt.Add(10 * time.Minute)) { 32 | returnError(w, fmt.Errorf("code expired")) 33 | return 34 | } 35 | if loginRequest.ClientID != r.PostForm.Get("client_id") { 36 | returnError(w, fmt.Errorf("client_id mismatch")) 37 | return 38 | } 39 | if loginRequest.AppConfig.ClientSecret != r.PostForm.Get("client_secret") { 40 | returnError(w, fmt.Errorf("invalid client_secret")) 41 | return 42 | } 43 | if loginRequest.RedirectURI != r.PostForm.Get("redirect_uri") { 44 | returnError(w, fmt.Errorf("invalid redirect_uri")) 45 | return 46 | } 47 | 48 | privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(s.PrivateKey) 49 | if err != nil { 50 | returnError(w, fmt.Errorf("private key parsing error: %s", err)) 51 | return 52 | } 53 | claims := jwt.MapClaims{ 54 | "iss": s.Config.Url, 55 | "sub": loginRequest.User.Sub, 56 | "aud": loginRequest.ClientID, 57 | "exp": time.Now().Add(1 * time.Hour).Unix(), 58 | "nbf": time.Now().Unix(), 59 | "iat": time.Now().Unix(), 60 | } 61 | token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) 62 | token.Header["kid"] = "0-0-0-1" 63 | 64 | signedIDToken, err := token.SignedString(privateKey) 65 | if err != nil { 66 | returnError(w, fmt.Errorf("signedString error: %s", err)) 67 | return 68 | } 69 | 70 | // access token 71 | claims = jwt.MapClaims{ 72 | "iss": s.Config.Url, 73 | "sub": loginRequest.User.Sub, 74 | "aud": []string{ 75 | s.Config.Url + "/userinfo", 76 | }, 77 | "exp": time.Now().Add(1 * time.Hour).Unix(), 78 | "nbf": time.Now().Unix(), 79 | "iat": time.Now().Unix(), 80 | } 81 | token = jwt.NewWithClaims(jwt.SigningMethodRS256, claims) 82 | token.Header["kid"] = "0-0-0-1" 83 | 84 | signedAccessToken, err := token.SignedString(privateKey) 85 | if err != nil { 86 | returnError(w, fmt.Errorf("signedString error: %s", err)) 87 | return 88 | } 89 | 90 | tokenOutput := oidc.Token{ 91 | AccessToken: signedAccessToken, 92 | IDToken: signedIDToken, 93 | TokenType: "bearer", 94 | ExpiresIn: 60, 95 | } 96 | 97 | delete(s.Codes, r.PostForm.Get("code")) 98 | 99 | out, err := json.Marshal(tokenOutput) 100 | if err != nil { 101 | returnError(w, fmt.Errorf("token marshal error: %s", err)) 102 | return 103 | } 104 | w.Write(out) 105 | } 106 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/types.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/users" 7 | ) 8 | 9 | type Config struct { 10 | Apps map[string]AppConfig `yaml:"apps"` 11 | Url string `yaml:"url"` 12 | LoadError error 13 | } 14 | type AppConfig struct { 15 | ClientID string `yaml:"clientID"` 16 | ClientSecret string `yaml:"clientSecret"` 17 | Issuer string `yaml:"issuer"` 18 | RedirectURIs []string `yaml:"redirectURIs"` 19 | } 20 | 21 | type LoginRequest struct { 22 | ClientID string 23 | RedirectURI string 24 | Scope string 25 | ResponseType string 26 | State string 27 | CodeIssuedAt time.Time 28 | User users.User 29 | AppConfig AppConfig 30 | } 31 | -------------------------------------------------------------------------------- /oidc-demo/pkg/server/userinfo.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/golang-jwt/jwt/v4" 10 | "github.com/wardviaene/golang-for-devops-course/oidc-demo/pkg/users" 11 | ) 12 | 13 | func (s *server) userinfo(w http.ResponseWriter, r *http.Request) { 14 | authorizationHeader := r.Header.Get("Authorization") 15 | 16 | if authorizationHeader == "" { 17 | returnError(w, fmt.Errorf("Authorization header empty")) 18 | return 19 | } 20 | 21 | authorizationHeader = strings.Replace(authorizationHeader, "Bearer ", "", -1) 22 | 23 | claims := &jwt.RegisteredClaims{} 24 | _, err := jwt.ParseWithClaims(authorizationHeader, claims, func(token *jwt.Token) (interface{}, error) { 25 | privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(s.PrivateKey) 26 | if err != nil { 27 | return nil, fmt.Errorf("Parse private key error: %s", err) 28 | } 29 | return &privateKey.PublicKey, nil 30 | }) 31 | if err != nil { 32 | returnError(w, fmt.Errorf("parse token error: %s", err)) 33 | return 34 | } 35 | 36 | found := false 37 | for _, aud := range claims.Audience { 38 | if aud == s.Config.Url+"/userinfo" { 39 | found = true 40 | } 41 | } 42 | if !found { 43 | returnError(w, fmt.Errorf("token has incorrect audience: %s", strings.Join(claims.Audience, ", "))) 44 | return 45 | } 46 | if claims.Subject == "" { 47 | returnError(w, fmt.Errorf("subject is empty")) 48 | return 49 | } 50 | 51 | for _, user := range users.GetAllUsers() { 52 | if user.Sub == claims.Subject { 53 | out, err := json.Marshal(user) 54 | if err != nil { 55 | returnError(w, fmt.Errorf("json marshal error: %s", err)) 56 | return 57 | } 58 | w.Write(out) 59 | return 60 | } 61 | } 62 | 63 | returnError(w, fmt.Errorf("user not found")) 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /oidc-demo/pkg/users/auth.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import "fmt" 4 | 5 | type User struct { 6 | Sub string `json:"sub"` 7 | Name string `json:"name"` 8 | GivenName string `json:"given_name"` 9 | FamilyName string `json:"family_name"` 10 | PreferredUsername string `json:"preferred_username"` 11 | Email string `json:"email"` 12 | Picture string `json:"picture"` 13 | } 14 | 15 | func Auth(login, password, mfa string) (bool, User, error) { 16 | if login == "edward" && password == "password" { 17 | return true, GetAllUsers()[0], nil 18 | } 19 | return false, User{}, fmt.Errorf("Invalid login or password") 20 | } 21 | 22 | func GetAllUsers() []User { 23 | return []User{ 24 | { 25 | Sub: "9-9-9-9", 26 | Name: "Edward Viaene", 27 | GivenName: "Edward", 28 | FamilyName: "Viaene", 29 | PreferredUsername: "edward", 30 | Email: "edward@domain.inv", 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /oidc-start/.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | -------------------------------------------------------------------------------- /oidc-start/cmd/appserver/jwt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/golang-jwt/jwt/v4" 4 | 5 | // gets token from tokenUrl validating token with jwksUrl and returning token & claims 6 | func getTokenFromCode(tokenUrl, jwksUrl, redirectUri, clientID, clientSecret, code string) (*jwt.Token, *jwt.StandardClaims, error) { 7 | return nil, nil, nil 8 | } 9 | -------------------------------------------------------------------------------- /oidc-start/cmd/appserver/jwt_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | "net/http" 10 | "net/http/httptest" 11 | "testing" 12 | "time" 13 | 14 | "github.com/golang-jwt/jwt/v4" 15 | "github.com/wardviaene/golang-for-devops-course/oidc-start/pkg/oidc" 16 | ) 17 | 18 | func TestGetTokenFromCode(t *testing.T) { 19 | // generate private key 20 | privateKey, err := rsa.GenerateKey(rand.Reader, 4096) 21 | if err != nil { 22 | t.Fatalf("Couldn't generate rsa key") 23 | } 24 | 25 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 | // generate jwt and return 27 | if r.URL.Path == "/token" { 28 | claims := jwt.MapClaims{ 29 | "iss": "http://example.com", 30 | "sub": "1-2-3-5", 31 | "aud": "1-2-3-4", 32 | "exp": time.Now().Add(1 * time.Hour).Unix(), 33 | "iat": time.Now().Unix(), 34 | } 35 | token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) 36 | token.Header["kid"] = "0-0-0-1" 37 | 38 | idTokenString, err := token.SignedString(privateKey) 39 | if err != nil { 40 | t.Fatalf("SignedString: %s", err) 41 | } 42 | // generate Access Token 43 | claims = jwt.MapClaims{ 44 | "iss": "http://example.com", 45 | "sub": "1-2-3-4", 46 | "aud": []string{ 47 | "http://example.com/userinfo", 48 | }, 49 | "exp": time.Now().Add(1 * time.Hour).Unix(), 50 | "iat": time.Now().Unix(), 51 | } 52 | token = jwt.NewWithClaims(jwt.SigningMethodRS256, claims) 53 | token.Header["kid"] = "0-0-0-1" 54 | 55 | accessTokenString, err := token.SignedString(privateKey) 56 | if err != nil { 57 | t.Fatalf("SignedString: %s", err) 58 | } 59 | 60 | refreshToken, err := oidc.GetRandomString(64) 61 | if err != nil { 62 | t.Fatalf("random string error: %s", err) 63 | } 64 | responseToken := oidc.Token{ 65 | IDToken: idTokenString, 66 | AccessToken: accessTokenString, 67 | ExpiresIn: 60, 68 | TokenType: "bearer", 69 | RefreshToken: refreshToken, 70 | } 71 | 72 | out, err := json.Marshal(responseToken) 73 | if err != nil { 74 | t.Fatalf("json marshal error: %s", err) 75 | return 76 | } 77 | w.Write(out) 78 | } else if r.URL.Path == "/jwks.json" { 79 | jwks := oidc.Jwks{ 80 | Keys: []oidc.JwksKey{ 81 | { 82 | N: base64.StdEncoding.EncodeToString(privateKey.PublicKey.N.Bytes()), 83 | E: "AQAB", 84 | Alg: "RS256", 85 | Use: "sig", 86 | Kid: "0-0-0-1", 87 | Kty: "RSA", 88 | }, 89 | }, 90 | } 91 | out, err := json.Marshal(jwks) 92 | if err != nil { 93 | t.Fatalf("jwks marshall error: %s", err) 94 | return 95 | } 96 | w.Write(out) 97 | } 98 | })) 99 | defer ts.Close() 100 | 101 | _, claims, err := getTokenFromCode(ts.URL+"/token", ts.URL+"/jwks.json", "http://localhost:8081", "1-2-3-4", "secret", "mycode") 102 | if err != nil { 103 | t.Fatalf("getTokenFromCode error: %s", err) 104 | } 105 | if claims == nil { 106 | t.Fatalf("claims is nil") 107 | } 108 | 109 | fmt.Printf("Exchanged code into token. Subject: %s\n", claims.Subject) 110 | 111 | } 112 | -------------------------------------------------------------------------------- /oidc-start/cmd/appserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | const redirectUri = "http://localhost:8081/callback" 9 | 10 | type app struct { 11 | } 12 | 13 | func main() { 14 | 15 | a := app{} 16 | 17 | http.HandleFunc("/", a.index) 18 | http.HandleFunc("/callback", a.callback) 19 | 20 | err := http.ListenAndServe(":8081", nil) 21 | if err != nil { 22 | fmt.Printf("ListenAndServe error: %s\n", err) 23 | } 24 | } 25 | 26 | func (a *app) index(w http.ResponseWriter, r *http.Request) { 27 | } 28 | 29 | func (a *app) callback(w http.ResponseWriter, r *http.Request) { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /oidc-start/cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | "github.com/wardviaene/golang-for-devops-course/oidc-start/pkg/server" 12 | "github.com/wardviaene/golang-for-devops-course/ssh-demo" 13 | ) 14 | 15 | const configFile = "" 16 | 17 | func main() { 18 | var ( 19 | privateKey []byte 20 | err error 21 | ) 22 | // read config 23 | if _, err = os.Stat(configFile); errors.Is(err, os.ErrNotExist) { 24 | fmt.Printf("Error: %s doesn't exist\n", configFile) 25 | os.Exit(1) 26 | } 27 | config, err := ioutil.ReadFile(configFile) 28 | if err != nil { 29 | log.Fatalf("Failed to load %s, err: %v", configFile, err) 30 | } 31 | // read encryption key 32 | if _, err = os.Stat("enckey.pem"); errors.Is(err, os.ErrNotExist) { 33 | if privateKey, _, err = ssh.GenerateKeys(); err != nil { 34 | fmt.Printf("Error: %s\n", err) 35 | os.Exit(1) 36 | } 37 | if err = os.WriteFile("enckey.pem", privateKey, 0600); err != nil { 38 | fmt.Printf("Error: %s\n", err) 39 | os.Exit(1) 40 | } 41 | } else { 42 | privateKey, err = ioutil.ReadFile("enckey.pem") 43 | if err != nil { 44 | log.Fatalf("Failed to load authorized_keys, err: %v", err) 45 | } 46 | 47 | } 48 | fmt.Printf("Server stopped: %s", server.Start(&http.Server{Addr: ":8080"}, privateKey, server.ReadConfig(config))) 49 | } 50 | -------------------------------------------------------------------------------- /oidc-start/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/oidc-start 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/golang-jwt/jwt/v4 v4.4.2 7 | github.com/wardviaene/golang-for-devops-course/ssh-demo v0.0.0-20220616215025-d61a2b0cee5f 8 | gopkg.in/yaml.v3 v3.0.1 9 | ) 10 | 11 | require ( 12 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect 13 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect 14 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /oidc-start/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= 2 | github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 3 | github.com/wardviaene/golang-for-devops-course/ssh-demo v0.0.0-20220616215025-d61a2b0cee5f h1:hsbzhLUNJIwe7Kj45QVOvvVvqqMZM42NQVBtiQVWVe8= 4 | github.com/wardviaene/golang-for-devops-course/ssh-demo v0.0.0-20220616215025-d61a2b0cee5f/go.mod h1:nMRrSdJ6buN2/nVCX4zAr1VTIzeZNqK7SgTepO1zRdA= 5 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= 6 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 7 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= 8 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 9 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= 10 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 14 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 15 | -------------------------------------------------------------------------------- /oidc-start/pkg/oidc/discovery.go: -------------------------------------------------------------------------------- 1 | package oidc 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | func ParseDiscovery(url string) (Discovery, error) { 10 | var discovery Discovery 11 | res, err := http.Get(url) 12 | if err != nil { 13 | return discovery, err 14 | } 15 | defer res.Body.Close() 16 | 17 | body, err := io.ReadAll(res.Body) 18 | 19 | if err = json.Unmarshal(body, &discovery); err != nil { 20 | return discovery, err 21 | } 22 | return discovery, nil 23 | } 24 | -------------------------------------------------------------------------------- /oidc-start/pkg/oidc/rand.go: -------------------------------------------------------------------------------- 1 | package oidc 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | "strings" 9 | ) 10 | 11 | func GetRandomString(n int) (string, error) { 12 | buf := make([]byte, n) 13 | 14 | _, err := io.ReadFull(rand.Reader, buf) 15 | if err != nil { 16 | return "", fmt.Errorf("crypto/rand is unavailable: Read() failed with %#v", err) 17 | } 18 | 19 | randomStr := base64.URLEncoding.EncodeToString(buf) 20 | randomStr = strings.Replace(randomStr, "=", "", -1) 21 | 22 | return randomStr, nil 23 | } 24 | -------------------------------------------------------------------------------- /oidc-start/pkg/oidc/types.go: -------------------------------------------------------------------------------- 1 | package oidc 2 | 3 | // discovery 4 | type Discovery struct { 5 | Issuer string `json:"issuer"` 6 | AuthorizationEndpoint string `json:"authorization_endpoint"` 7 | TokenEndpoint string `json:"token_endpoint"` 8 | UserinfoEndpoint string `json:"userinfo_endpoint"` 9 | JwksURI string `json:"jwks_uri"` 10 | ScopesSupported []string `json:"scopes_supported"` 11 | ResponseTypesSupported []string `json:"response_types_supported"` 12 | TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"` 13 | } 14 | 15 | // token 16 | type Token struct { 17 | AccessToken string `json:"access_token"` 18 | TokenType string `json:"token_type"` 19 | RefreshToken string `json:"refresh_token"` 20 | ExpiresIn int `json:"expires_in"` 21 | IDToken string `json:"id_token"` 22 | } 23 | 24 | // jwks 25 | type Jwks struct { 26 | Keys []JwksKey `json:"keys"` 27 | } 28 | type JwksKey struct { 29 | N string `json:"n"` 30 | E string `json:"e"` 31 | Alg string `json:"alg"` 32 | Use string `json:"use"` 33 | Kid string `json:"kid"` 34 | Kty string `json:"kty"` 35 | } 36 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/authorization.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func (s *server) authorization(w http.ResponseWriter, r *http.Request) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/authorization_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestAuthorization(t *testing.T) { 12 | s := newServer(privkeyPem, testConfig) 13 | 14 | endpoint := fmt.Sprintf("/authorization?client_id=%s&client_secret=%s&redirect_uri=%s&scope=openid&response_type=code&state=randomstring", 15 | s.Config.Apps["app1"].ClientID, 16 | s.Config.Apps["app1"].ClientSecret, 17 | s.Config.Apps["app1"].RedirectURIs[0], 18 | ) 19 | req := httptest.NewRequest(http.MethodGet, endpoint, nil) 20 | w := httptest.NewRecorder() 21 | s.authorization(w, req) 22 | res := w.Result() 23 | defer res.Body.Close() 24 | _, err := io.ReadAll(res.Body) 25 | if err != nil { 26 | t.Errorf("Readall error: %s", err) 27 | } 28 | 29 | if res.Header.Get("location") == "" { 30 | t.Fatalf("Location header not set") 31 | } 32 | 33 | if res.StatusCode != http.StatusFound { 34 | t.Fatalf("HTTP StatusCode: %d (expected %d)", res.StatusCode, http.StatusAccepted) 35 | } 36 | 37 | fmt.Printf("Got location: %s\n", res.Header.Get("location")) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/config.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | func ReadConfig(bytes []byte) Config { 4 | var config Config 5 | 6 | // config parsing 7 | 8 | return config 9 | } 10 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/config_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/discovery.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func (s *server) discovery(w http.ResponseWriter, r *http.Request) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/http.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type server struct { 8 | PrivateKey []byte 9 | Config Config 10 | } 11 | 12 | func newServer(privateKey []byte, config Config) *server { 13 | return &server{ 14 | PrivateKey: privateKey, 15 | Config: config, 16 | } 17 | } 18 | 19 | func Start(httpServer *http.Server, privateKey []byte, config Config) error { 20 | s := newServer(privateKey, config) 21 | 22 | http.HandleFunc("/authorization", s.authorization) 23 | http.HandleFunc("/token", s.token) 24 | http.HandleFunc("/login", s.login) 25 | http.HandleFunc("/jwks.json", s.jwks) 26 | http.HandleFunc("/.well-known/openid-configuration", s.discovery) 27 | http.HandleFunc("/userinfo", s.userinfo) 28 | 29 | return httpServer.ListenAndServe() 30 | } 31 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/http_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "log" 10 | "net/http" 11 | "os" 12 | "strings" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | /* Global code for all tests */ 18 | var privkeyPem []byte 19 | 20 | var testConfig Config 21 | 22 | func TestMain(m *testing.M) { 23 | err := testSetup() 24 | if err != nil { 25 | log.Fatalf("test setup failed: %s", err) 26 | } 27 | code := m.Run() 28 | os.Exit(code) 29 | } 30 | 31 | func testSetup() error { 32 | privKey, err := rsa.GenerateKey(rand.Reader, 4096) 33 | if err != nil { 34 | return err 35 | } 36 | privkeyBytes := x509.MarshalPKCS1PrivateKey(privKey) 37 | privkeyPem = pem.EncodeToMemory( 38 | &pem.Block{ 39 | Type: "RSA PRIVATE KEY", 40 | Bytes: privkeyBytes, 41 | }, 42 | ) 43 | // populate testConfig 44 | testConfig = Config{ 45 | Apps: map[string]AppConfig{ 46 | "app1": { 47 | ClientID: "1-2-3-4", 48 | ClientSecret: "secret", 49 | Issuer: "http://localhost:8080", 50 | RedirectURIs: []string{"http://localhost:8082/callback"}, 51 | }, 52 | }, 53 | } 54 | return nil 55 | } 56 | 57 | func TestStart(t *testing.T) { 58 | httpServer := &http.Server{Addr: ":8080"} 59 | 60 | go func() { 61 | err := Start(httpServer, privkeyPem, testConfig) 62 | if err != nil && err.Error() != "http: Server closed" { 63 | t.Errorf("Start error: %s\n", err) 64 | } 65 | }() 66 | 67 | time.Sleep(1 * time.Second) // give time for the http server to start 68 | 69 | endpoints := []string{"/authorization", "token", "login", "jwks.json", "/.well-known/openid-configuration", "userinfo"} 70 | for _, endpoint := range endpoints { 71 | addr := httpServer.Addr 72 | if strings.HasPrefix(addr, ":") { 73 | addr = "http://localhost" + addr 74 | } else { 75 | addr = "http://" + addr 76 | } 77 | res, err := http.Get(addr + "/" + endpoint) 78 | if err != nil { 79 | t.Fatalf("http get error: %s", err) 80 | } 81 | if res.StatusCode == 404 || res.StatusCode >= 500 { 82 | t.Errorf("Endpoint %s not available. Statuscode: %d", endpoint, res.StatusCode) 83 | } 84 | } 85 | 86 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 87 | defer cancel() 88 | if err := httpServer.Shutdown(ctx); err != nil { 89 | t.Fatalf("Could not shut down http server") 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/jwks.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func (s *server) jwks(w http.ResponseWriter, r *http.Request) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/login.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "embed" 5 | "net/http" 6 | ) 7 | 8 | //go:embed templates/* 9 | var templateFs embed.FS 10 | 11 | func (s *server) login(w http.ResponseWriter, r *http.Request) { 12 | // to access the login template: 13 | // templateFile, err := templateFs.Open("templates/login.html") 14 | } 15 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/login_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/http/httptest" 9 | "net/url" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func TestLoginGet(t *testing.T) { 15 | s := newServer(privkeyPem, testConfig) 16 | 17 | // 1. authorization flow 18 | endpoint := fmt.Sprintf("/authorization?client_id=%s&client_secret=%s&redirect_uri=%s&scope=openid&response_type=code&state=randomstring", 19 | s.Config.Apps["app1"].ClientID, 20 | s.Config.Apps["app1"].ClientSecret, 21 | s.Config.Apps["app1"].RedirectURIs[0], 22 | ) 23 | req := httptest.NewRequest(http.MethodGet, endpoint, nil) 24 | w := httptest.NewRecorder() 25 | s.authorization(w, req) 26 | res := w.Result() 27 | defer res.Body.Close() 28 | _, err := io.ReadAll(res.Body) 29 | if err != nil { 30 | t.Errorf("Readall error: %s", err) 31 | } 32 | 33 | if res.Header.Get("location") == "" { 34 | t.Fatalf("Location header not set") 35 | } 36 | 37 | // 2. login flow 38 | req = httptest.NewRequest(http.MethodGet, res.Header.Get("location"), nil) 39 | w = httptest.NewRecorder() 40 | s.login(w, req) 41 | loginRes := w.Result() 42 | defer loginRes.Body.Close() 43 | body, err := io.ReadAll(loginRes.Body) 44 | if err != nil { 45 | t.Errorf("Readall error: %s", err) 46 | } 47 | if loginRes.StatusCode != http.StatusOK { 48 | t.Fatalf("HTTP StatusCode: %d (expected %d)", res.StatusCode, http.StatusAccepted) 49 | } 50 | 51 | if !strings.Contains(strings.ToLower(string(body)), " 2 | 3 | 4 | 5 | 6 | 7 | Login example 8 | 9 | 10 | 11 |
12 |
13 |
14 |

Login form

15 |

Login form example from templates/login.html

16 | 17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 |
26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/token.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func (s *server) token(w http.ResponseWriter, r *http.Request) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/types.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | type Config struct { 4 | Apps map[string]AppConfig 5 | Url string 6 | LoadError error 7 | } 8 | type AppConfig struct { 9 | ClientID string 10 | ClientSecret string 11 | Issuer string 12 | RedirectURIs []string 13 | } 14 | -------------------------------------------------------------------------------- /oidc-start/pkg/server/userinfo.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func (s *server) userinfo(w http.ResponseWriter, r *http.Request) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /oidc-start/pkg/users/auth.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import "fmt" 4 | 5 | type User struct { 6 | Sub string `json:"sub"` 7 | Name string `json:"name"` 8 | GivenName string `json:"given_name"` 9 | FamilyName string `json:"family_name"` 10 | PreferredUsername string `json:"preferred_username"` 11 | Email string `json:"email"` 12 | Picture string `json:"picture"` 13 | } 14 | 15 | func Auth(login, password, mfa string) (bool, User, error) { 16 | if login == "edward" && password == "password" { 17 | return true, GetAllUsers()[0], nil 18 | } 19 | return false, User{}, fmt.Errorf("Invalid login or password") 20 | } 21 | 22 | func GetAllUsers() []User { 23 | return []User{ 24 | { 25 | Sub: "9-9-9-9", 26 | Name: "edward", 27 | GivenName: "Edward", 28 | FamilyName: "Viaene", 29 | PreferredUsername: "edward", 30 | Email: "edward@domain.inv", 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /reader-example/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/reader-example 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /reader-example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | ) 8 | 9 | type MySlowReader struct { 10 | contents string 11 | pos int 12 | } 13 | 14 | func (m *MySlowReader) Read(p []byte) (int, error) { 15 | if m.pos+1 <= len(m.contents) { 16 | n := copy(p, m.contents[m.pos:m.pos+1]) 17 | m.pos++ 18 | return n, nil 19 | } 20 | return 0, io.EOF 21 | } 22 | 23 | func main() { 24 | myReaderInstance := &MySlowReader{ 25 | contents: "a", 26 | } 27 | out, err := io.ReadAll(myReaderInstance) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | fmt.Printf("output: %s", out) 32 | } 33 | -------------------------------------------------------------------------------- /slices-demo/cmd/array-and-slice/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var arr1 [7]int = [7]int{7, 3, 6, 0, 4, 9, 10} 7 | fmt.Println(arr1) 8 | fmt.Printf("%d %d\n", len(arr1), cap(arr1)) 9 | var arr2 []int = arr1[1:3] 10 | fmt.Println(arr2) 11 | fmt.Printf("%d %d\n", len(arr2), cap(arr2)) 12 | arr2 = arr2[0 : len(arr2)+2] 13 | fmt.Println(arr2) 14 | fmt.Printf("%d %d\n", len(arr2), cap(arr2)) 15 | for k := range arr2 { 16 | arr2[k] += 1 17 | } 18 | fmt.Println(arr2) 19 | fmt.Printf("%d %d\n", len(arr2), cap(arr2)) 20 | fmt.Println(arr1) 21 | 22 | var arr3 []int = []int{1, 2, 3} 23 | fmt.Println(arr3) 24 | fmt.Printf("%d %d\n", len(arr3), cap(arr3)) 25 | arr3 = append(arr3, 4) 26 | fmt.Println(arr3) 27 | fmt.Printf("%d %d\n", len(arr3), cap(arr3)) 28 | arr3 = append(arr3, 5) 29 | fmt.Println(arr3) 30 | fmt.Printf("%d %d\n", len(arr3), cap(arr3)) 31 | 32 | arr4 := make([]int, 3, 9) 33 | fmt.Println(arr4) 34 | fmt.Printf("%d %d\n", len(arr4), cap(arr4)) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /slices-demo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/slices-demo 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /ssh-demo/.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | *.pub 3 | -------------------------------------------------------------------------------- /ssh-demo/cmd/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | 8 | "golang.org/x/crypto/ssh" 9 | ) 10 | 11 | func main() { 12 | var ( 13 | err error 14 | ) 15 | privateKey, err := ioutil.ReadFile("mykey.pem") 16 | if err != nil { 17 | log.Fatalf("Failed to load mykey.pem, err: %v", err) 18 | } 19 | publicKey, err := ioutil.ReadFile("server.pub") 20 | if err != nil { 21 | log.Fatalf("Failed to load server.pub, err: %v", err) 22 | } 23 | 24 | privateKeyParsed, err := ssh.ParsePrivateKey(privateKey) 25 | if err != nil { 26 | log.Fatalf("Failed to parse mykey.pem, err: %v", err) 27 | } 28 | publicKeyParsed, _, _, _, err := ssh.ParseAuthorizedKey(publicKey) 29 | if err != nil { 30 | log.Fatalf("Failed to parse server.pub, err: %v", err) 31 | } 32 | 33 | config := &ssh.ClientConfig{ 34 | User: "username", 35 | Auth: []ssh.AuthMethod{ 36 | ssh.PublicKeys(privateKeyParsed), 37 | }, 38 | HostKeyCallback: ssh.FixedHostKey(publicKeyParsed), 39 | } 40 | client, err := ssh.Dial("tcp", "localhost:2022", config) 41 | if err != nil { 42 | log.Fatal("Failed to dial: ", err) 43 | } 44 | defer client.Close() 45 | 46 | session, err := client.NewSession() 47 | if err != nil { 48 | log.Fatal("NewSession error: ", err) 49 | } 50 | 51 | defer session.Close() 52 | 53 | out, err := session.Output("whoami") 54 | if err != nil { 55 | log.Fatal("Session output error: ", err) 56 | } 57 | 58 | fmt.Printf("Output is: %s\n", out) 59 | } 60 | -------------------------------------------------------------------------------- /ssh-demo/cmd/keygen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/wardviaene/golang-for-devops-course/ssh-demo" 8 | ) 9 | 10 | func main() { 11 | var ( 12 | privateKey []byte 13 | publicKey []byte 14 | err error 15 | ) 16 | if privateKey, publicKey, err = ssh.GenerateKeys(); err != nil { 17 | fmt.Printf("Error: %s\n", err) 18 | os.Exit(1) 19 | } 20 | if err = os.WriteFile("mykey.pem", privateKey, 0600); err != nil { 21 | fmt.Printf("Error: %s\n", err) 22 | os.Exit(1) 23 | } 24 | if err = os.WriteFile("mykey.pub", publicKey, 0644); err != nil { 25 | fmt.Printf("Error: %s\n", err) 26 | os.Exit(1) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /ssh-demo/cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "github.com/wardviaene/golang-for-devops-course/ssh-demo" 10 | ) 11 | 12 | func main() { 13 | var ( 14 | err error 15 | ) 16 | serverKeyBytes, err := ioutil.ReadFile("server.pem") 17 | if err != nil { 18 | log.Fatalf("Failed to load authorized_keys, err: %v", err) 19 | } 20 | 21 | authorizedKeysBytes, err := ioutil.ReadFile("mykey.pub") 22 | if err != nil { 23 | log.Fatalf("Failed to load authorized_keys, err: %v", err) 24 | } 25 | 26 | if err = ssh.StartServer(serverKeyBytes, authorizedKeysBytes); err != nil { 27 | fmt.Printf("Error: %s\n", err) 28 | os.Exit(1) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /ssh-demo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/ssh-demo 2 | 3 | go 1.18 4 | 5 | require ( 6 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect 7 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect 8 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /ssh-demo/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= 2 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 3 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 4 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= 5 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 6 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= 7 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 8 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= 9 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 10 | -------------------------------------------------------------------------------- /ssh-demo/keygen.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | 9 | "golang.org/x/crypto/ssh" 10 | ) 11 | 12 | func GenerateKeys() ([]byte, []byte, error) { 13 | privateKey, err := rsa.GenerateKey(rand.Reader, 4096) 14 | if err != nil { 15 | return nil, nil, err 16 | } 17 | 18 | privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)} 19 | 20 | pubKey, err := ssh.NewPublicKey(&privateKey.PublicKey) 21 | if err != nil { 22 | return nil, nil, err 23 | } 24 | 25 | return pem.EncodeToMemory(privateKeyPEM), ssh.MarshalAuthorizedKey(pubKey), nil 26 | } 27 | -------------------------------------------------------------------------------- /ssh-demo/server.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "net" 8 | 9 | "golang.org/x/crypto/ssh" 10 | "golang.org/x/term" 11 | ) 12 | 13 | func StartServer(privateKey []byte, authorizedKeys []byte) error { 14 | authorizedKeysMap := map[string]bool{} 15 | for len(authorizedKeys) > 0 { 16 | pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeys) 17 | if err != nil { 18 | return fmt.Errorf("Parse Authorized keys error: %s", err) 19 | } 20 | 21 | authorizedKeysMap[string(pubKey.Marshal())] = true 22 | authorizedKeys = rest 23 | } 24 | 25 | config := &ssh.ServerConfig{ 26 | PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { 27 | if authorizedKeysMap[string(pubKey.Marshal())] { 28 | return &ssh.Permissions{ 29 | // Record the public key used for authentication. 30 | Extensions: map[string]string{ 31 | "pubkey-fp": ssh.FingerprintSHA256(pubKey), 32 | }, 33 | }, nil 34 | } 35 | return nil, fmt.Errorf("unknown public key for %q", c.User()) 36 | }, 37 | } 38 | 39 | private, err := ssh.ParsePrivateKey(privateKey) 40 | if err != nil { 41 | return fmt.Errorf("ParsePrivateKey error: %s", err) 42 | } 43 | 44 | config.AddHostKey(private) 45 | 46 | // Once a ServerConfig has been configured, connections can be 47 | // accepted. 48 | listener, err := net.Listen("tcp", "0.0.0.0:2022") 49 | if err != nil { 50 | return fmt.Errorf("Listen error: %s", err) 51 | } 52 | 53 | for { 54 | nConn, err := listener.Accept() 55 | if err != nil { 56 | fmt.Printf("Listener accept error: %s\n", err) 57 | } 58 | 59 | // Before use, a handshake must be performed on the incoming 60 | // net.Conn. 61 | conn, chans, reqs, err := ssh.NewServerConn(nConn, config) 62 | if err != nil { 63 | fmt.Printf("NewServerConn error: %s\n", err) 64 | } 65 | if conn != nil && conn.Permissions != nil { 66 | log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"]) 67 | } 68 | 69 | // The incoming Request channel must be serviced. 70 | go ssh.DiscardRequests(reqs) 71 | 72 | go handleConnection(conn, chans) 73 | } 74 | } 75 | 76 | func handleConnection(conn *ssh.ServerConn, chans <-chan ssh.NewChannel) { 77 | // Service the incoming Channel channel. 78 | for newChannel := range chans { 79 | // Channels have a type, depending on the application level 80 | // protocol intended. In the case of a shell, the type is 81 | // "session" and ServerShell may be used to present a simple 82 | // terminal interface. 83 | if newChannel.ChannelType() != "session" { 84 | newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") 85 | continue 86 | } 87 | channel, requests, err := newChannel.Accept() 88 | if err != nil { 89 | fmt.Printf("Could not accept channel: %v\n", err) 90 | continue // not shown in demo lecture, but we can skip this loop iteration when there's an error 91 | } 92 | 93 | // Sessions have out-of-band requests such as "shell", 94 | // "pty-req" and "env". Here we handle only the 95 | // "shell" request. 96 | go func(in <-chan *ssh.Request) { 97 | for req := range in { 98 | fmt.Printf("Request Type made by client: %s\n", req.Type) 99 | switch req.Type { 100 | case "exec": 101 | payload := bytes.TrimPrefix(req.Payload, []byte{0, 0, 0, 6}) 102 | channel.Write([]byte(execSomething(conn, payload))) 103 | channel.SendRequest("exit-status", false, []byte{0, 0, 0, 0}) 104 | req.Reply(true, nil) 105 | channel.Close() 106 | case "shell": 107 | req.Reply(true, nil) 108 | case "pty-req": 109 | createTerminal(conn, channel) 110 | default: 111 | req.Reply(false, nil) 112 | } 113 | 114 | } 115 | }(requests) 116 | } 117 | } 118 | 119 | func createTerminal(conn *ssh.ServerConn, channel ssh.Channel) { 120 | termInstance := term.NewTerminal(channel, "> ") 121 | go func() { 122 | defer channel.Close() 123 | for { 124 | line, err := termInstance.ReadLine() 125 | if err != nil { 126 | fmt.Printf("ReadLine error: %s", err) 127 | break 128 | } 129 | switch line { 130 | case "whoami": 131 | termInstance.Write([]byte(execSomething(conn, []byte("whoami")))) 132 | case "": 133 | case "quit": 134 | termInstance.Write([]byte("Goodbye!\n")) 135 | channel.Close() 136 | default: 137 | termInstance.Write([]byte("Command not found\n")) 138 | } 139 | } 140 | }() 141 | } 142 | 143 | func execSomething(conn *ssh.ServerConn, payload []byte) string { 144 | switch string(payload) { 145 | case "whoami": 146 | return fmt.Sprintf("You are: %s\n", conn.Conn.User()) 147 | default: 148 | return fmt.Sprintf("Command Not Found: %s\n", string(payload)) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /test-server/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Build go project 3 | # 4 | FROM golang:1.18-alpine as go-builder 5 | 6 | WORKDIR /app 7 | 8 | COPY . . 9 | 10 | RUN apk add -u -t build-tools curl git && \ 11 | go build -o server *.go && \ 12 | apk del build-tools && \ 13 | rm -rf /var/cache/apk/* 14 | 15 | # 16 | # Runtime container 17 | # 18 | FROM alpine:latest 19 | 20 | WORKDIR /app 21 | 22 | RUN apk --no-cache add ca-certificates 23 | 24 | COPY --from=go-builder /app/server /app/server 25 | 26 | EXPOSE 8080 27 | 28 | ENTRYPOINT ["/app/server"] 29 | -------------------------------------------------------------------------------- /test-server/Dockerfile.scratch: -------------------------------------------------------------------------------- 1 | # 2 | # Build go project 3 | # 4 | FROM golang:1.18-alpine as go-builder 5 | 6 | WORKDIR /app 7 | 8 | COPY . . 9 | 10 | RUN apk add -u -t build-tools curl git && \ 11 | CGO_ENABLED=0 go build -o server *.go && \ 12 | apk del build-tools && \ 13 | rm -rf /var/cache/apk/* 14 | 15 | # 16 | # Runtime container 17 | # 18 | FROM scratch 19 | 20 | COPY --from=go-builder /app/server /server 21 | 22 | EXPOSE 8080 23 | 24 | ENTRYPOINT ["/server"] 25 | -------------------------------------------------------------------------------- /test-server/README.md: -------------------------------------------------------------------------------- 1 | # test-server 2 | 3 | The test server is used in multiple lectures. You can use the following command to start the test-server: 4 | ``` 5 | ./start-test-server.sh 6 | ``` 7 | 8 | # Notes 9 | If you're using zsh, make sure to use quotes around the URL when testing. 10 | -------------------------------------------------------------------------------- /test-server/assignment1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/rand" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type assignment1 struct { 12 | Page string `json:"page"` 13 | Words []string `json:"words"` 14 | Percentages map[string]float64 `json:"percentages"` 15 | Special []*string `json:"special"` 16 | ExtraSpecial []any `json:"extraSpecial"` 17 | } 18 | 19 | func (ct *WordsHandler) assignment1(w http.ResponseWriter, r *http.Request) { 20 | one := "one" 21 | two := "two" 22 | words := []string{"one", "two", "three", "four", "five", "six", "seven", "eigth", "nine", "ten"} 23 | numbers := []float64{0.33, 0.66, 0.1, 0, 1, 0.99, 0.88, 0.5, 0.1, 0.2} 24 | rand.Seed(time.Now().UnixNano()) 25 | percentages := make(map[string]float64) 26 | wordsRand := make([]string, 5) 27 | for i := 0; i < 5; i++ { 28 | randomInt := rand.Intn(9) 29 | wordsRand[i] = words[randomInt] 30 | percentages[words[randomInt]] = numbers[randomInt] 31 | } 32 | wordsOutput := assignment1{ 33 | Page: "assignment1", 34 | Words: wordsRand, 35 | Percentages: percentages, 36 | Special: []*string{&one, &two, nil}, 37 | ExtraSpecial: []any{1, 2, "3"}, 38 | } 39 | out, err := json.Marshal(wordsOutput) 40 | if err != nil { 41 | fmt.Fprintf(w, "marshal error") 42 | return 43 | } 44 | fmt.Fprint(w, string(out)) 45 | } 46 | -------------------------------------------------------------------------------- /test-server/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/go-for-devops-course/test-server 2 | 3 | go 1.18 4 | 5 | require github.com/golang-jwt/jwt/v4 v4.4.2 // indirect 6 | -------------------------------------------------------------------------------- /test-server/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= 2 | github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 3 | github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= 4 | github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 5 | -------------------------------------------------------------------------------- /test-server/ratelimit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | const DATE_FORMAT = "2006-01-02T15:04:05" 11 | 12 | type RateLimit struct { 13 | mu sync.Mutex 14 | hits map[string]uint64 15 | limitExceeded bool 16 | limitLifted time.Time 17 | } 18 | 19 | func (rl *RateLimit) ratelimit(w http.ResponseWriter, r *http.Request) { 20 | if rl.limitExceeded && time.Now().Before(rl.limitLifted) { 21 | w.WriteHeader(429) 22 | w.Write([]byte("Rate Limited")) 23 | return 24 | } 25 | if rl.limitExceeded && time.Now().After(rl.limitLifted) { 26 | rl.limitExceeded = false 27 | } 28 | rl.mu.Lock() 29 | timestamp := time.Now() 30 | strTimestamp := timestamp.Format(DATE_FORMAT) 31 | if val, ok := rl.hits[strTimestamp]; ok { 32 | if val == 5 { 33 | rl.limitExceeded = true 34 | rl.limitLifted = time.Now().Add(time.Second * 10) 35 | } else { 36 | rl.hits[strTimestamp] = val + 1 37 | } 38 | } else { 39 | rl.hits[strTimestamp] = 1 40 | } 41 | rl.mu.Unlock() 42 | timestampOneSecondEarlier := time.Now().Add(time.Duration(-1) * time.Second) 43 | if rl.hits[timestampOneSecondEarlier.Format(DATE_FORMAT)] == 5 { 44 | w.Write([]byte(fmt.Sprintf("DONE! You did it! Hitting API at %d requests in a given second\n", rl.hits[timestampOneSecondEarlier.Format(DATE_FORMAT)]))) 45 | } else { 46 | w.Write([]byte(fmt.Sprintf("Hitting API at %d requests in a given second\n", rl.hits[timestampOneSecondEarlier.Format(DATE_FORMAT)]))) 47 | } 48 | if len(rl.hits) > 100000 { 49 | rl.mu.Lock() 50 | fmt.Printf("Map is getting big, resetting...\n") 51 | oldVal := rl.hits[strTimestamp] 52 | rl.hits = make(map[string]uint64) 53 | rl.hits[strTimestamp] = oldVal 54 | time.Sleep(1 * time.Second) 55 | rl.mu.Unlock() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test-server/start-test-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ./start-test-server.sh 3 | # You can use this script to start the server 4 | go get "github.com/golang-jwt/jwt/v4" 5 | go run assignment1.go main.go ratelimit.go 6 | -------------------------------------------------------------------------------- /tls-demo/.gitignore: -------------------------------------------------------------------------------- 1 | /*.pem 2 | /*.crt 3 | /*.key 4 | -------------------------------------------------------------------------------- /tls-demo/cmd/letsencrypt-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "golang.org/x/crypto/acme/autocert" 9 | ) 10 | 11 | func index(w http.ResponseWriter, req *http.Request) { 12 | fmt.Fprintf(w, "it's working") 13 | } 14 | func main() { 15 | http.HandleFunc("/", index) 16 | err := http.Serve(autocert.NewListener("go-demo-test.newtech.academy"), nil) 17 | if err != nil { 18 | log.Fatal("ListenAndServeTLS error: ", err) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tls-demo/cmd/mtls-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | caBytes, err := ioutil.ReadFile("ca.crt") 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | ca := x509.NewCertPool() 20 | if !ca.AppendCertsFromPEM(caBytes) { 21 | log.Fatal("ca.cert not valid") 22 | } 23 | 24 | cert, err := tls.LoadX509KeyPair("client.crt", "client.key") 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | client := http.Client{ 30 | Timeout: 60 * time.Second, 31 | Transport: &http.Transport{ 32 | TLSClientConfig: &tls.Config{ 33 | RootCAs: ca, 34 | Certificates: []tls.Certificate{cert}, 35 | }, 36 | }, 37 | } 38 | resp, err := client.Get("https://go-demo.localtest.me/common-name") 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | defer resp.Body.Close() 43 | body, err := io.ReadAll(resp.Body) 44 | 45 | fmt.Printf("Body (status %d): %s\n", resp.StatusCode, body) 46 | } 47 | -------------------------------------------------------------------------------- /tls-demo/cmd/mtls-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | func index(w http.ResponseWriter, req *http.Request) { 13 | fmt.Fprintf(w, "it's working") 14 | } 15 | 16 | func showCommonName(w http.ResponseWriter, req *http.Request) { 17 | var commonName string 18 | if req.TLS != nil && len(req.TLS.VerifiedChains) > 0 && len(req.TLS.VerifiedChains[0]) > 0 { 19 | commonName = req.TLS.VerifiedChains[0][0].Subject.CommonName 20 | } 21 | fmt.Fprintf(w, "Your common name: %s", commonName) 22 | } 23 | 24 | func main() { 25 | caBytes, err := ioutil.ReadFile("ca.crt") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | ca := x509.NewCertPool() 30 | if !ca.AppendCertsFromPEM(caBytes) { 31 | log.Fatal("ca.cert not valid") 32 | } 33 | 34 | http.HandleFunc("/", index) 35 | http.HandleFunc("/common-name", showCommonName) 36 | server := http.Server{ 37 | Addr: ":443", 38 | TLSConfig: &tls.Config{ 39 | ClientAuth: tls.RequireAndVerifyClientCert, 40 | ClientCAs: ca, 41 | MinVersion: tls.VersionTLS13, 42 | }, 43 | } 44 | err = server.ListenAndServeTLS("server.crt", "server.key") 45 | if err != nil { 46 | log.Fatal("ListenAndServeTLS error: ", err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tls-demo/cmd/test-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func index(w http.ResponseWriter, req *http.Request) { 10 | fmt.Fprintf(w, "it's working") 11 | } 12 | func main() { 13 | http.HandleFunc("/", index) 14 | err := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil) 15 | if err != nil { 16 | log.Fatal("ListenAndServeTLS error: ", err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tls-demo/cmd/tls/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/wardviaene/golang-for-devops-course/tls-demo/pkg/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /tls-demo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/tls-demo 2 | 3 | replace github.com/golang/mock v1.4.3 => github.com/golang/mock v1.4.4 4 | 5 | go 1.18 6 | 7 | require ( 8 | github.com/spf13/cobra v1.3.0 9 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa 10 | gopkg.in/yaml.v2 v2.4.0 11 | ) 12 | 13 | require ( 14 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 15 | github.com/spf13/pflag v1.0.5 // indirect 16 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect 17 | golang.org/x/text v0.3.7 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /tls-demo/pkg/cert/pem.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | import ( 4 | "crypto/x509" 5 | "encoding/pem" 6 | "fmt" 7 | ) 8 | 9 | func PemToX509(input []byte) (*x509.Certificate, error) { 10 | block, _ := pem.Decode(input) 11 | if block == nil { 12 | return nil, fmt.Errorf("failed to parse certificate PEM") 13 | } 14 | return x509.ParseCertificate(block.Bytes) 15 | } 16 | -------------------------------------------------------------------------------- /tls-demo/pkg/cert/types.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | import "math/big" 4 | 5 | type CACert struct { 6 | Serial *big.Int `yaml:"serial"` 7 | ValidForYears int `yaml:"validForYears"` 8 | Subject CertSubject `yaml:"subject"` 9 | } 10 | type Cert struct { 11 | Serial *big.Int `yaml:"serial"` 12 | ValidForYears int `yaml:"validForYears"` 13 | Subject CertSubject `yaml:"subject"` 14 | DNSNames []string `yaml:"dnsNames"` 15 | } 16 | type CertSubject struct { 17 | Country string `yaml:"country"` 18 | Organization string `yaml:"organization"` 19 | OrganizationalUnit string `yaml:"organizationalUnit"` 20 | Locality string `yaml:"locality"` 21 | Province string `yaml:"province"` 22 | StreetAddress string `yaml:"streetAddress"` 23 | PostalCode string `yaml:"postalCode"` 24 | SerialNumber string `yaml:"serialNumber"` 25 | CommonName string `yaml:"commonName"` 26 | } 27 | -------------------------------------------------------------------------------- /tls-demo/pkg/cert/x509.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/pem" 10 | "io/ioutil" 11 | "time" 12 | 13 | "github.com/wardviaene/golang-for-devops-course/tls-demo/pkg/key" 14 | ) 15 | 16 | func CreateCACert(ca *CACert, keyFilePath, caCertFilePath string) error { 17 | template := &x509.Certificate{ 18 | SerialNumber: ca.Serial, 19 | Subject: pkix.Name{ 20 | Country: removeEmptyString([]string{ca.Subject.Country}), 21 | Organization: removeEmptyString([]string{ca.Subject.Organization}), 22 | OrganizationalUnit: removeEmptyString([]string{ca.Subject.OrganizationalUnit}), 23 | Locality: removeEmptyString([]string{ca.Subject.Locality}), 24 | Province: removeEmptyString([]string{ca.Subject.Province}), 25 | StreetAddress: removeEmptyString([]string{ca.Subject.StreetAddress}), 26 | PostalCode: removeEmptyString([]string{ca.Subject.PostalCode}), 27 | CommonName: ca.Subject.CommonName, 28 | }, 29 | NotBefore: time.Now(), 30 | NotAfter: time.Now().AddDate(ca.ValidForYears, 0, 0), 31 | IsCA: true, 32 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 33 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 34 | BasicConstraintsValid: true, 35 | } 36 | 37 | keyBytes, certBytes, err := createCert(template, nil, nil) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | if err := ioutil.WriteFile(keyFilePath, keyBytes, 0600); err != nil { 43 | return err 44 | } 45 | if err := ioutil.WriteFile(caCertFilePath, certBytes, 0644); err != nil { 46 | return err 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func CreateCert(cert *Cert, caKey []byte, caCert []byte, keyFilePath, certFilePath string) error { 53 | template := &x509.Certificate{ 54 | SerialNumber: cert.Serial, 55 | Subject: pkix.Name{ 56 | Country: removeEmptyString([]string{cert.Subject.Country}), 57 | Organization: removeEmptyString([]string{cert.Subject.Organization}), 58 | OrganizationalUnit: removeEmptyString([]string{cert.Subject.OrganizationalUnit}), 59 | Locality: removeEmptyString([]string{cert.Subject.Locality}), 60 | Province: removeEmptyString([]string{cert.Subject.Province}), 61 | StreetAddress: removeEmptyString([]string{cert.Subject.StreetAddress}), 62 | PostalCode: removeEmptyString([]string{cert.Subject.PostalCode}), 63 | CommonName: cert.Subject.CommonName, 64 | }, 65 | NotBefore: time.Now(), 66 | NotAfter: time.Now().AddDate(cert.ValidForYears, 0, 0), 67 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 68 | KeyUsage: x509.KeyUsageDigitalSignature, 69 | DNSNames: removeEmptyString(cert.DNSNames), 70 | } 71 | 72 | caKeyParsed, err := key.PrivateKeyPemToRSA(caKey) 73 | if err != nil { 74 | return err 75 | } 76 | caCertParsed, err := PemToX509(caCert) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | keyBytes, certBytes, err := createCert(template, caKeyParsed, caCertParsed) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | if err := ioutil.WriteFile(keyFilePath, keyBytes, 0600); err != nil { 87 | return err 88 | } 89 | if err := ioutil.WriteFile(certFilePath, certBytes, 0644); err != nil { 90 | return err 91 | } 92 | 93 | return nil 94 | } 95 | 96 | func createCert(template *x509.Certificate, caKey *rsa.PrivateKey, caCert *x509.Certificate) ([]byte, []byte, error) { 97 | var ( 98 | derBytes []byte 99 | certOut bytes.Buffer 100 | keyOut bytes.Buffer 101 | ) 102 | 103 | privateKey, err := key.CreateRSAPrivateKey(4096) 104 | if err != nil { 105 | return nil, nil, err 106 | } 107 | if template.IsCA { 108 | derBytes, err = x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey) 109 | if err != nil { 110 | return nil, nil, err 111 | } 112 | } else { 113 | derBytes, err = x509.CreateCertificate(rand.Reader, template, caCert, &privateKey.PublicKey, caKey) 114 | if err != nil { 115 | return nil, nil, err 116 | } 117 | } 118 | 119 | if err = pem.Encode(&certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { 120 | return nil, nil, err 121 | } 122 | if err = pem.Encode(&keyOut, key.RSAPrivateKeyToPEM(privateKey)); err != nil { 123 | return nil, nil, err 124 | } 125 | 126 | return keyOut.Bytes(), certOut.Bytes(), nil 127 | } 128 | 129 | func removeEmptyString(input []string) []string { 130 | if len(input) == 1 && input[0] == "" { 131 | return []string{} 132 | } 133 | return input 134 | } 135 | -------------------------------------------------------------------------------- /tls-demo/pkg/cmd/ca.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/wardviaene/golang-for-devops-course/tls-demo/pkg/cert" 8 | ) 9 | 10 | var caKey string 11 | var caCert string 12 | 13 | func init() { 14 | createCmd.AddCommand(caCreateCmd) 15 | caCreateCmd.Flags().StringVarP(&caKey, "key-out", "k", "ca.key", "destination path for ca key") 16 | caCreateCmd.Flags().StringVarP(&caCert, "cert-out", "o", "ca.crt", "destination path for ca cert") 17 | } 18 | 19 | var caCreateCmd = &cobra.Command{ 20 | Use: "ca", 21 | Short: "ca commands", 22 | Long: `commands to create the CA`, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | err := cert.CreateCACert(config.CACert, caKey, caCert) 25 | if err != nil { 26 | fmt.Printf("Create CA error: %s\n", err) 27 | return 28 | } 29 | fmt.Printf("CA created. Key: %s, cert: %s\n", caKey, caCert) 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /tls-demo/pkg/cmd/cert.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/wardviaene/golang-for-devops-course/tls-demo/pkg/cert" 9 | ) 10 | 11 | var certKeyPath string 12 | var certPath string 13 | var certName string 14 | 15 | func init() { 16 | createCmd.AddCommand(certCreateCmd) 17 | certCreateCmd.Flags().StringVarP(&certKeyPath, "key-out", "k", "server.key", "destination path for cert key") 18 | certCreateCmd.Flags().StringVarP(&certPath, "cert-out", "o", "server.crt", "destination path for cert cert") 19 | certCreateCmd.Flags().StringVarP(&certName, "name", "n", "", "name of the certificate in the config file") 20 | certCreateCmd.Flags().StringVar(&caKey, "ca-key", "ca.key", "ca key to sign certificate") 21 | certCreateCmd.Flags().StringVar(&caCert, "ca-cert", "ca.crt", "ca cert for certificate") 22 | certCreateCmd.MarkFlagRequired("ca-key") 23 | certCreateCmd.MarkFlagRequired("ca-cert") 24 | certCreateCmd.MarkFlagRequired("name") 25 | } 26 | 27 | var certCreateCmd = &cobra.Command{ 28 | Use: "cert", 29 | Short: "cert commands", 30 | Long: `commands to create the certificates`, 31 | Run: func(cmd *cobra.Command, args []string) { 32 | caKeyBytes, err := ioutil.ReadFile(caKey) 33 | if err != nil { 34 | fmt.Printf("CA key read error: %s\n", err) 35 | return 36 | } 37 | caCertBytes, err := ioutil.ReadFile(caCert) 38 | if err != nil { 39 | fmt.Printf("CA cert read error: %s\n", err) 40 | return 41 | } 42 | certConfig, ok := config.Cert[certName] 43 | if !ok { 44 | fmt.Printf("Could not find certificate name in config\n") 45 | return 46 | } 47 | err = cert.CreateCert(certConfig, caKeyBytes, caCertBytes, certKeyPath, certPath) 48 | if err != nil { 49 | fmt.Printf("Create cert error: %s\n", err) 50 | return 51 | } 52 | fmt.Printf("Cert created. Key: %s, cert: %s\n", certKeyPath, certPath) 53 | }, 54 | } 55 | -------------------------------------------------------------------------------- /tls-demo/pkg/cmd/create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func init() { 6 | rootCmd.AddCommand(createCmd) 7 | } 8 | 9 | var createCmd = &cobra.Command{ 10 | Use: "create", 11 | Short: "create CA, certs, or keys", 12 | Long: `commands to create resources (ca, certs, keys)`, 13 | } 14 | -------------------------------------------------------------------------------- /tls-demo/pkg/cmd/key.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/wardviaene/golang-for-devops-course/tls-demo/pkg/key" 8 | ) 9 | 10 | var keyOut string 11 | var keyLength int 12 | 13 | func init() { 14 | createCmd.AddCommand(keyCreateCmd) 15 | keyCreateCmd.Flags().StringVarP(&keyOut, "key-out", "k", "key.pem", "destination path for key") 16 | keyCreateCmd.Flags().IntVarP(&keyLength, "key-length", "l", 4096, "key length") 17 | } 18 | 19 | var keyCreateCmd = &cobra.Command{ 20 | Use: "key", 21 | Short: "key commands", 22 | Long: `commands to create keys`, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | err := key.CreateRSAPrivateKeyAndSave(keyOut, keyLength) 25 | if err != nil { 26 | fmt.Printf("Create key error: %s\n", err) 27 | return 28 | } 29 | fmt.Printf("Key created %s with length %d\n", keyOut, keyLength) 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /tls-demo/pkg/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | "github.com/wardviaene/golang-for-devops-course/tls-demo/pkg/cert" 10 | "gopkg.in/yaml.v2" 11 | ) 12 | 13 | type Config struct { 14 | CACert *cert.CACert `yaml:"caCert"` 15 | Cert map[string]*cert.Cert `yaml:"certs"` 16 | } 17 | 18 | var cfgFilePath string 19 | var config Config 20 | 21 | var rootCmd = &cobra.Command{ 22 | Use: "tls", 23 | Short: "tls is a command line tool for TLS.", 24 | Long: `tls is a command line tool for TLS. 25 | Mainly used for generation of X.509 certificates, but can be extended`, 26 | } 27 | 28 | func Execute() { 29 | if err := rootCmd.Execute(); err != nil { 30 | fmt.Fprintln(os.Stderr, err) 31 | os.Exit(1) 32 | } 33 | } 34 | 35 | func init() { 36 | cobra.OnInitialize(initConfig) 37 | 38 | rootCmd.PersistentFlags().StringVarP(&cfgFilePath, "config", "c", "", "config file (default is tls.yaml)") 39 | } 40 | 41 | func initConfig() { 42 | if cfgFilePath == "" { 43 | cfgFilePath = "tls.yaml" 44 | } 45 | cfgFileBytes, err := ioutil.ReadFile(cfgFilePath) 46 | if err != nil { 47 | fmt.Printf("Error while reading config file: %s\n", err) 48 | return 49 | } 50 | err = yaml.Unmarshal(cfgFileBytes, &config) 51 | if err != nil { 52 | fmt.Printf("Error while parsing config file: %s\n", err) 53 | return 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tls-demo/pkg/key/rsa.go: -------------------------------------------------------------------------------- 1 | package key 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "fmt" 9 | "os" 10 | ) 11 | 12 | func CreateRSAPrivateKey(n int) (*rsa.PrivateKey, error) { 13 | return rsa.GenerateKey(rand.Reader, n) 14 | } 15 | 16 | func RSAPrivateKeyToPEM(privateKey *rsa.PrivateKey) *pem.Block { 17 | return &pem.Block{ 18 | Type: "RSA PRIVATE KEY", 19 | Bytes: x509.MarshalPKCS1PrivateKey(privateKey), 20 | } 21 | } 22 | 23 | func CreateRSAPrivateKeyAndSave(path string, n int) error { 24 | privateKey, err := CreateRSAPrivateKey(n) 25 | if err != nil { 26 | return err 27 | } 28 | f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600) 29 | if err != nil { 30 | return err 31 | } 32 | if err := pem.Encode(f, RSAPrivateKeyToPEM(privateKey)); err != nil { 33 | return err 34 | } 35 | if err := f.Close(); err != nil { 36 | return err 37 | } 38 | return nil 39 | } 40 | 41 | func PrivateKeyPemToRSA(input []byte) (*rsa.PrivateKey, error) { 42 | var parsedKey *rsa.PrivateKey 43 | var err error 44 | 45 | privPem, _ := pem.Decode(input) 46 | 47 | if privPem.Type != "RSA PRIVATE KEY" { 48 | return nil, fmt.Errorf("RSA private key is of the wrong type: %s", privPem.Type) 49 | } 50 | 51 | if parsedKey, err = x509.ParsePKCS1PrivateKey(privPem.Bytes); err != nil { 52 | return nil, fmt.Errorf("Unable to parse RSA private key: %v", err) 53 | } 54 | 55 | return parsedKey, nil 56 | } 57 | -------------------------------------------------------------------------------- /tls-demo/tls.yaml: -------------------------------------------------------------------------------- 1 | caCert: 2 | serial: 1 3 | validForYears: 10 4 | subject: 5 | country: US 6 | organization: Golang Demo Org 7 | organizationalUnit: Certificate Management 8 | locality: NY 9 | commonName: CA Certificate 10 | certs: 11 | go-demo.localtest.me: 12 | serial: 1 13 | validForYears: 1 14 | dnsNames: ["go-demo.localtest.me", "go-demo-2.localtest.me"] 15 | subject: 16 | country: US 17 | organization: Golang Demo Org 18 | organizationalUnit: go-demo department 19 | locality: NY 20 | commonName: go-demo.localtest.me 21 | go-demo-client.localtest.me: 22 | serial: 1 23 | validForYears: 1 24 | subject: 25 | country: US 26 | organization: Golang Demo Org 27 | organizationalUnit: go-demo department 28 | locality: NY 29 | commonName: go-demo-client.localtest.me -------------------------------------------------------------------------------- /tls-start/.gitignore: -------------------------------------------------------------------------------- 1 | /*.pem 2 | /*.crt 3 | /*.key 4 | -------------------------------------------------------------------------------- /tls-start/cmd/test-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func index(w http.ResponseWriter, req *http.Request) { 10 | fmt.Fprintf(w, "it's working") 11 | } 12 | func main() { 13 | http.HandleFunc("/", index) 14 | err := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil) 15 | if err != nil { 16 | log.Fatal("ListenAndServeTLS error: ", err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tls-start/cmd/tls/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/wardviaene/golang-for-devops-course/tls-start/pkg/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /tls-start/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/tls-start 2 | 3 | replace github.com/golang/mock v1.4.3 => github.com/golang/mock v1.4.4 4 | 5 | go 1.18 6 | 7 | require ( 8 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 9 | github.com/spf13/cobra v1.5.0 // indirect 10 | github.com/spf13/pflag v1.0.5 // indirect 11 | gopkg.in/yaml.v2 v2.4.0 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /tls-start/go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 3 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 4 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 5 | github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= 6 | github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= 7 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 8 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 11 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 12 | -------------------------------------------------------------------------------- /tls-start/pkg/cert/pem.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | import ( 4 | "crypto/x509" 5 | "encoding/pem" 6 | "fmt" 7 | ) 8 | 9 | func PemToX509(input []byte) (*x509.Certificate, error) { 10 | block, _ := pem.Decode(input) 11 | if block == nil { 12 | return nil, fmt.Errorf("failed to parse certificate PEM") 13 | } 14 | return x509.ParseCertificate(block.Bytes) 15 | } 16 | -------------------------------------------------------------------------------- /tls-start/pkg/cert/types.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | import "math/big" 4 | 5 | type CACert struct { 6 | Serial *big.Int `yaml:"serial"` 7 | ValidForYears int `yaml:"validForYears"` 8 | Subject CertSubject `yaml:"subject"` 9 | } 10 | type Cert struct { 11 | Serial *big.Int `yaml:"serial"` 12 | ValidForYears int `yaml:"validForYears"` 13 | Subject CertSubject `yaml:"subject"` 14 | DNSNames []string `yaml:"dnsNames"` 15 | } 16 | type CertSubject struct { 17 | Country string `yaml:"country"` 18 | Organization string `yaml:"organization"` 19 | OrganizationalUnit string `yaml:"organizationalUnit"` 20 | Locality string `yaml:"locality"` 21 | Province string `yaml:"province"` 22 | StreetAddress string `yaml:"streetAddress"` 23 | PostalCode string `yaml:"postalCode"` 24 | SerialNumber string `yaml:"serialNumber"` 25 | CommonName string `yaml:"commonName"` 26 | } 27 | -------------------------------------------------------------------------------- /tls-start/pkg/cert/x509.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | func CreateCACert(ca *CACert, keyFilePath, caCertFilePath string) error { 4 | return nil 5 | } 6 | 7 | func CreateCert(cert *Cert, caKey []byte, caCert []byte, keyFilePath, certFilePath string) error { 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /tls-start/pkg/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | func Execute() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tls-start/pkg/key/rsa.go: -------------------------------------------------------------------------------- 1 | package key 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "fmt" 9 | "os" 10 | ) 11 | 12 | func CreateRSAPrivateKey(n int) (*rsa.PrivateKey, error) { 13 | return rsa.GenerateKey(rand.Reader, n) 14 | } 15 | 16 | func RSAPrivateKeyToPEM(privateKey *rsa.PrivateKey) *pem.Block { 17 | return &pem.Block{ 18 | Type: "RSA PRIVATE KEY", 19 | Bytes: x509.MarshalPKCS1PrivateKey(privateKey), 20 | } 21 | } 22 | 23 | func CreateRSAPrivateKeyAndSave(path string, n int) error { 24 | privateKey, err := CreateRSAPrivateKey(n) 25 | if err != nil { 26 | return err 27 | } 28 | f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600) 29 | if err != nil { 30 | return err 31 | } 32 | if err := pem.Encode(f, RSAPrivateKeyToPEM(privateKey)); err != nil { 33 | return err 34 | } 35 | if err := f.Close(); err != nil { 36 | return err 37 | } 38 | return nil 39 | } 40 | 41 | func PrivateKeyPemToRSA(input []byte) (*rsa.PrivateKey, error) { 42 | var parsedKey *rsa.PrivateKey 43 | var err error 44 | 45 | privPem, _ := pem.Decode(input) 46 | 47 | if privPem.Type != "RSA PRIVATE KEY" { 48 | return nil, fmt.Errorf("RSA private key is of the wrong type: %s", privPem.Type) 49 | } 50 | 51 | if parsedKey, err = x509.ParsePKCS1PrivateKey(privPem.Bytes); err != nil { 52 | return nil, fmt.Errorf("Unable to parse RSA private key: %v", err) 53 | } 54 | 55 | return parsedKey, nil 56 | } 57 | -------------------------------------------------------------------------------- /tls-start/tls.yaml: -------------------------------------------------------------------------------- 1 | caCert: 2 | serial: 1 3 | validForYears: 10 4 | subject: 5 | country: US 6 | organization: Golang Demo Org 7 | organizationalUnit: Certificate Management 8 | locality: NY 9 | commonName: CA Certificate 10 | certs: 11 | go-demo.localtest.me: 12 | serial: 1 13 | validForYears: 1 14 | dnsNames: ["go-demo.localtest.me", "go-demo-2.localtest.me"] 15 | subject: 16 | country: US 17 | organization: Golang Demo Org 18 | organizationalUnit: go-demo department 19 | locality: NY 20 | commonName: go-demo.localtest.me 21 | -------------------------------------------------------------------------------- /types-demo/cmd/generics/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func main() { 9 | var t1 int = 123 10 | fmt.Printf("plusOne: %v (type: %s)\n", plusOne(t1), reflect.TypeOf(plusOne(t1))) 11 | var t2 float64 = 123.12 12 | fmt.Printf("plusOne: %v (type: %s)\n", plusOne(t2), reflect.TypeOf(plusOne(t2))) 13 | fmt.Printf("sum: %v (type: %s)\n", sum(t1, t1), reflect.TypeOf(sum(t1, t1))) 14 | fmt.Printf("sum: %v (type: %s)\n", sum(t2, t2), reflect.TypeOf(sum(t2, t2))) 15 | //fmt.Printf("sum: %v (type: %s)\n", sum(t1, t2), reflect.TypeOf(sum(t1, t2))) 16 | } 17 | 18 | func plusOne[V int | float64 | int64 | float32 | int32](t V) V { 19 | return t + 1 20 | } 21 | 22 | func sum[V int | float64 | int64 | float32 | int32](t1 V, t2 V) V { 23 | return t1 + t2 24 | } 25 | -------------------------------------------------------------------------------- /types-demo/cmd/json-parsing/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "reflect" 8 | ) 9 | 10 | type MyJson struct { 11 | Test any `json:"test"` 12 | Test3 string `json:"test3"` 13 | } 14 | 15 | func main() { 16 | var jsonParsed MyJson 17 | err := json.Unmarshal([]byte(`{"test": { "test2": [1,2,3] }, "test3": "..." }`), &jsonParsed) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | switch v := jsonParsed.Test.(type) { 22 | case map[string]any: 23 | fmt.Printf("Map found: %v\n", v) 24 | field1, ok := v["test2"] 25 | if ok { 26 | switch v2 := field1.(type) { 27 | case []any: 28 | fmt.Printf("I found a []any\n") 29 | for _, v2Element := range v2 { 30 | fmt.Printf("Type: %s\n", reflect.TypeOf(v2Element)) 31 | if reflect.TypeOf(v2Element).String() == "float64" { 32 | fmt.Printf("Int: %d\n", int(v2Element.(float64))) 33 | } else { 34 | fmt.Printf("Didn't recognize v2Element\n") 35 | } 36 | } 37 | default: 38 | fmt.Printf("Type not found: %s\n", reflect.TypeOf(v2)) 39 | } 40 | } 41 | default: 42 | fmt.Printf("Type not found: %s\n", reflect.TypeOf(jsonParsed)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /types-demo/cmd/type-switch/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func main() { 9 | var t1 string = "this is a string" 10 | var t2 *string = &t1 11 | discoverType(t2) 12 | var t3 int = 123 13 | discoverType(t3) 14 | discoverType(nil) 15 | } 16 | 17 | func discoverType(t any) { 18 | switch v := t.(type) { 19 | case string: 20 | t2 := v + "..." 21 | fmt.Printf("String found: %s\n", t2) 22 | case *string: 23 | fmt.Printf("Pointer string found: %s\n", *v) 24 | case int: 25 | fmt.Printf("We have an integer: %d\n", v) 26 | default: 27 | myType := reflect.TypeOf(t) 28 | if myType == nil { 29 | fmt.Printf("type is nil\n") 30 | } else { 31 | fmt.Printf("Type not found: %s\n", myType) 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /types-demo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wardviaene/golang-for-devops-course/types-demo 2 | 3 | go 1.19 4 | --------------------------------------------------------------------------------