Contents
- Why Am I Doing All this?
- What Will We Find Here?
- First Some Concepts:
- Current State For the Scripts
- How To Use the Scripts
- The “Fun” Part: How Did I Crappy Code it?
- What Did I Learn?
- What is Next?
Why Am I Doing All this?
Before you crucify me with questions or comments like:
- Why to automate such a simple task?
- Why Go and not Python or even bash?
- Why Mirantis Kubernetes Engine (MKE) and not any other Kubernetes cluster?
- This is crappy code!
Please, allow me to go through the why I coded, wrote and posted this:
- As part of my job, I have to do repros* in the lab quite often, and the first step is usually dealing with authentication against a server or cluster.
- Yes, there might be easier and better ways, but I happen to like Go a lot, so no wonder.
- I use MKE everyday because of my job.
- Yes, it might be crappy code but I am learning and having fun with it. Also, I promise you, I will refactor it.
What Will We Find Here?
Two Go scripts:
1.GetAuthToken.go
To get a token to authenticate against MKE (Mirantis Kubernetes Engine) API.
2. GetClientBundle.go
To download the MKE client bundle.
First, Some Concepts
1. What is MKE?
Mirantis Kubernetes Engine (MKE, formerly Universal Control Plane or UCP) is a container orchestration platform for developing and running modern applications at scale, on private clouds, public clouds, and on bare metal.
For more information, visit the documentation: https://docs.mirantis.com/mke/3.5/overview.html
2. What is the Client Bundle?
The MKE client bundle allow us to use MKE with Docker CLI and kubectl. The bundle includes:
A private and public key pair for authorizing your requests using MKE
Utility scripts for configuring Docker CLI and kubectl with your MKE deployment
For additional information, visit the official documentation: https://docs.mirantis.com/mke/3.5/ops/access-cluster/download-configure-client-bundle.html
Current State For the Scripts
They are crappy code! I am not exactly proud of how they look at the moment but they work and that makes me happy (for now).
At this moment, the Go scripts successfully authenticate against the MKE API and download a client bundle.
How To Use the Scripts
If a picture is worth a thousand words, then videos should be even better. So, let’s watch how to use the scripts (GetAuthToken.go and GetCientBundle.go).
First, get the authentication token from your MKE:
Once you have the authentication token, you can request anything from the cluster. In this case, we will ask for the client bundle:
Yes! They are horribly hardcoded so you can not pass your Kubernetes cluster and/or the token as parameters, but I will fix it. I promise you!
The “Fun” Part: How Did I Crappy Code it?
1. Tried With Curl
Well, as said, there are better and/or easier ways to do this (quite subjective but ok…), and one is via “curl” as shown in the official documentation:
2. Used Postman to Get and Idea
Since I had this nicely documented, I just took the “curl” command and imported it into Postman to get the Go code generated. You will notice that the generated source code will work only for getting the token, but as for getting the client bundle itself, additional changes will be implemented.
Make sure to paste in here whatever is relevant for the “curl” command. For example, compared to the documentation, I removed the variable “AUTHTOKEN” and also there is no need to pipe anything to “jq”. So, this is how it looks:
Then, it is just a couple of clicks here and there, continue, imports…blah blah, whatever is needed:
At this point, Postman should get populated with something that in theory will do the same as the raw “curl” command we passed:
Let’s take a look at that:
In the red rectangles, I have highligthed what Postman “did”(and some few manual changes) for us; in green is higlighted what we need to click to test it, and the outputs are in pink:
We got a POST as HTTP method because we want to write or provide info(username and password). From the “raw curl” the “-d” option is indeed to specify this.
The “Body” contains the data we want to pass to the server. From the “curl” command in the documentation link we can see that the data is in JSON format. NOTE: Here I had to do a small change on what Postman populated, if you are following at this point, you will notice that “x-www-form-urlencoded” was the checkbox initially selected, but I changed it to “raw” to make it work.
The URL specifiying the server and resource to reach was also populated.
Once we have verified the request built by Postman, we can give it a try by hitting “Send” and we should get a “200 OK” as status code. Although even more important the “auth_token” that we will use in the subsequent requests to the server (MKE cluster) with the GetClientBundle.go script.
3. So, What Was the Purpose of This?
Remember that the “curl” command itself was working, so what? We tested it for the sake of it? Well, no. What I wanted was to get an idea in regards to the Go code needed for this. And, it happens that Postman has this handy feature to generate code for you. Here is how:
4. Does This Stuff Work? Does It Even Compile?
If it compiles, at least syntactically we have something valid to start to work on. So let’s see which Nicolas Cage’s category we are at.
Below you can see how to compile the Go code generated by Postman.
At this point, since you have already a binary, you might want to try if you can obtain the authentication token but if we are on the same page, then you will notice that we are facing our first roadblock:
❯ ./main
Post "https://192.168.100.100/auth/login": x509: certificate is valid for 10.96.0.1, 127.0.0.1, 172.31.20.61, not 192.168.100.100
We will workaround that error in the next section. No worries.
5. Some Errors, What Do We Do Now?
If you have seen this kind of error before, you might say: “It seems the IP(192.168.100.100) is not added to the certificate SAN list, let’s add it!”
Well, at least that is what I said the first time, which made me add the IP to the SAN list, give it a try and… “ta-dah”!!!
❯ ./main
Post "https://192.168.100.100/auth/login": x509: certificate signed by unknown authority
Once we see the above error, we know that there is a broken certificate trust chain. And, regardless if you are using your own CA or if this is a new MKE with a self signed certiticate, the idea of getting the MKE Client Bundle is to actually deal with all this. That being said, I decided to add something to the code to “ignore” the TLS verification… But, which part? How to do so?
First, let’s check what we have now and let’s break it down:
Omitting the parts needed for all Go code to work, in my mind this is more or less how our source code works:
- Crafting my Request: Defining URL, HTTP Method and payload(credentials):
req, err := http.NewRequest(method, url, payload)
- Loading the Client: Having an already “crafted” request, let’s give it to the client that will send it:
client := &http.Client{}
- Sending the Request: Executing the action of sending the request:
client.Do(req)
- Procesing the Response: Dealing with the response from the server/cluster:
body, err := ioutil.ReadAll(res.Body)
Next, I started to look into the Go standard library for something that could allow me to deal with the HTTPS/TLS part, and I found out about the “Transport”:
transCfg := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: transCfg}
Being implemented the “Transport” into the source code, we will be able to get the GetAuthToken.go finally working.
You can take a look at the functional source code of the GetAuthToken.go here.
6. We Got The Token, What Now?
Now, we can focus on the GetCientBundle.go script.
If we think about it, a good part of the logic and code we have used to get the token might be useful for getting the client bundle as well:
In a nutshell, GetAuthToken.go is a HTTP client that sends a POST with the credentials as payload to finally get the authentication token (text).
Keeping in mind the previous statement then:
In a nutshell, GetClientBundle.go should be a HTTP client that sends a GET with the token as a header to finally get the client bundle (gzip).
Then, to make my explanation shorter this time, what I have pretty much done is:
- Changed the HTTP Method from POST to GET.
- Added the authentication token in a HTTP Header.
- Glued some code to deal with the download of the client bundle file (gzip).
You can take a look at the final source code here.
7. Really, Is It Working?
Yes, it is finally working! Once you give it a try, it will look like this:
❯ ./GetAuthToken
{"auth_token":"896386ba-15f2-4bc6-9be4-99eaf10ca345"}
❯ ./GetClientBundle
Done.
❯ ls -al
total 32
drwxr-xr-x 3 aescobar staff 96 Apr 1 14:25 .
drwxr-xr-x 16 aescobar staff 512 Apr 1 14:22 ..
-rw-r--r-- 1 aescobar staff 15680 Apr 1 14:25 bundle.zip
❯ unzip bundle.zip
Archive: bundle.zip
extracting: clientbundle
❯ ls -al
total 64
drwxr-xr-x 4 aescobar staff 128 Apr 1 14:25 .
drwxr-xr-x 16 aescobar staff 512 Apr 1 14:22 ..
-rw-r--r-- 1 aescobar staff 15680 Apr 1 14:25 bundle.zip
-rw-r--r-- 1 aescobar staff 15524 Apr 1 14:25 clientbundle
What Did I Learn?
I prefer to say that I “got familiar” instead of “I learned”. The second one sounds a bit presumptuous to me given how deep and extensive each of these topics could be, but yeah, here it is:
- HTTP(S) Go Clients
- Set HTTP Headers for a HTTP Go client.
- A bit of TLS handling with Go.
What Is Next?
People! This is a work in progress… I am aware about multiple points where there is definitely room for improvement, but I really wanted to get this posted once it started to work.
I know, it is play code, or garbage one, it sucks! there are harcoded values, the code is not grouped in functions, etc etc etc. I am sorry my dear demanding code reader, I failed you…. :( at least for now, so here is where I will be working and (hopefully) I will post it:
- Refactor, refactor, refactor
- Group code in fuctions.
- Create a single script.
- Pass parameters to the script and avoid hardcoded values.
- Containeraization!
Finally, if anyone reads this, I would be extremly happy to hear any constructive feedback (about the code, writting style, memes, the content itself, layout of my blog/post, etc). Reach out preferibly on Twitter but don’t hold yourself if any other way is prefered.