Consul-Template returns empty map? - go-templates

Hi everyone,
I am trying to fetch a client token from approle auth method in vault. I have configured consul-template that it gets the role-id and the secret-id then, uses them to get the client_token at auth/approle/login end point. What is really weird is that first two steps are successfully executed and returned the data, however, getting the client_token is the struggle here. I analyzed the requests using wireshark and checked the response for the request that asks for the client_token, and it shows clearly the json result with the client_token included. But consul template does recognize the return and gives me this result:
{322a47b9-bf23-193d-8117-228637253fde 0 false map[] [] 0xc42001cf50 <nil>}.
The same way is used to request the secret-id but consul-template has successfully returned it and has recognized the json object. Isn't that weird?!
You can find the consul template below:
{{define "token" }}
{{ with secret "auth/approle/role/python-role/role-id" }}{{ $role:= (print "role_id= " .Data.role_id) }}
{{ with secret "auth/approle/role/python-role/secret-id" "role_name= python-role" }}{{ $secret:= (print "secret_id= " .Data.secret_id) }}
{{- with secret "auth/approle/login/" $role $secret -}}{{ . }}{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{template "token"}}
Also, below is the wireshark trace that I used to check that the request went right:
Does anyone have an idea?
Thanks in advance.

Related

Retry Ansible URI PUT API Call with a POST API Call

Hello I am trying to run a PUT API call via Ansible URI module for a particular API Endpoint in my application, using a dictionary that contains the json files and that is defined as:
example: { 'example1' : 'v1', 'example2': 'v2''}
- name: Update existing
block:
- name: update existing
uri:
url: "{{url}}/api/{{item.key}}/"
method: PUT
body: "{{ lookup('file', 'example/{{item.key}}/{{item.value}}.json') }}"
status_code: 200
body_format: json
headers:
Content-Type: "application/json"
Authorization: "Token {{ token.json.token }}"
with_dict: "{{ example }}"
register: result
For the PUT api call, this api endpoint will fail is the {{item.key}} does not exist, e.g. if
"{{url}}/api/{{item.key}}/" endpoint does not exist, hence it will give a 4xx error.
Given the task fails and I get a 4xx error when the api endpoint for the item does not exist, I want to run a POST command for that same json file.
How can I do this in ansible, to retry a task that failed but only specifically for that {{item.key}} and {{item.value}} in dictionary?
or
Is there a better way to do this to retry a failed PUT with a POST command
I want to use the ansible URI module
Thanks!
You can ignore the error case and then loop through result.results with filtering to keep only errors. You can have access to the original item with item.item:
- name: update existing with PUT
uri:
url: "{{url}}/api/{{item.key}}/"
method: PUT
body: "{{ lookup('file', 'example/{{item.key}}/{{item.value}}.json') }}"
status_code:
- 200
- 404 # supposing it's the "normal" error case
body_format: json
headers:
Content-Type: "application/json"
Authorization: "Token {{ token.json.token }}"
loop: "{{ example | items2dict }}"
register: result
- name: update existing with POST
uri:
url: "{{url}}/api/{{item.item.key}}/"
method: POSTT
body: "{{ lookup('file', 'example/{{item.item.key}}/{{item.item.value}}.json') }}"
status_code: 200
body_format: json
headers:
Content-Type: "application/json"
Authorization: "Token {{ token.json.token }}"
loop: "{{ result.results | rejectattr('status', '200') }}" # filter out 200 status from previous task

How to avoid passing sensitive data through values.yml (bitnami chart)

I am trying to deploy a rabbitmq-cluster on minikube based on the chart of Bitnami and facing the following challenge: When I try to pass the credentials using a secret I am getting this error: couldn't find key rabbitmq-password in Secret default/rabbit
I created a secret called rabbit in my minikube cluster and tryed to set the values-file like this:
auth:
# username: user
# password: pass
existingPasswordSecret: rabbit
and also like this:
auth:
username: ${RABBITMQ_USERNAME}
password: ${RABBITMQ_PASSWORD}
existingPasswordSecret: rabbit
This is my secret-file:
apiVersion: v1
kind: Secret
metadata:
name: rabbit
type: Opaque
data:
RABBITMQ_USERNAME: dXNlcg== (bitnami variable)
RABBITMQ_PASSWORD: cGFzcw== (bitnami variable)
This is the default secret of the chart (I am installing the chart using helm install rabbitmq -f rabbitmq/values.yml bitnami/rabbitmq):
apiVersion: v1
kind: Secret
metadata:
name: {{ include "rabbitmq.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels: {{- include "common.labels.standard" . | nindent 4 }}
type: Opaque
data:
{{- if not .Values.auth.existingPasswordSecret }}
{{- if .Values.auth.password }}
rabbitmq-password: {{ .Values.auth.password | b64enc | quote }}
{{- else }}
rabbitmq-password: {{ randAlphaNum 10 | b64enc | quote }}
{{- end }}
{{- end }}
The error message is telling you that you are missing the rabbitmq-password key in your secret:
couldn't find key rabbitmq-password in Secret default/rabbit
If we take a look at your secret, we can see you are providing two keys (RABBITMQ_USERNAME and RABBITMQ_PASSWORD), but not the rabbitmq-password key it expects:
apiVersion: v1
kind: Secret
metadata:
name: rabbit
type: Opaque
data:
RABBITMQ_USERNAME: dXNlcg== (bitnami variable)
RABBITMQ_PASSWORD: cGFzcw== (bitnami variable)
Knowing this, you have to provide your password using rabbitmq-password instead of RABBITMQ_PASSWORD. The chart does not provide support for passing the user as a secret, tho. Your secret should look like this:
apiVersion: v1
kind: Secret
metadata:
name: rabbit
type: Opaque
data:
rabbitmq-password: cGFzcw== (bitnami variable)

hitting an endpoint from angular8 returns 404

This is my first exercise in angular8. I am on the attempt to make a form that consumes an API written in springboot. The api written in spring-boot is never executed when trying to consume it from angular8 and here is the endpoint
http://localhost:8080/api/startreg
#PostMapping("/startreg")
public ResponseData<Activity> addReg(
#RequestParam(value="firstDate") String firstDate,#RequestParam(value="secondDate") String secondDate
,#RequestParam(value="username") String username) {
try {
Here is the service.ts script
private baseUrl = 'http://localhost:8080/api/startreg';
createReg(activity: Object): Observable<Object> {
return this.http.post('${this.baseUrl}', activity);
}
the html file of the angular8 is shown
<form (ngSubmit)="onSubmit()">
<div class="col-md-3 col-sm-5 col-xs-12 gutter">
<div class="sales">
<h2>From:</h2>
<div class="btn-group">
<select [(ngModel)]="activity.firstDate" class="form-control" name="firstDate">
when I attempt to submit the form, from the browser console below error
HttpErrorResponse {headers: HttpHeaders, status: 404, statusText: "Not Found", url: "http://localhost:4202/$%7Bthis.baseUrl%7D", ok: false, …}
Please where I am getting it wrong
Because this.http.post('${this.baseUrl}', activity); won't invoke service with the value of this.baseUrl, you can find out why according to the error message.
The URL you passed to post is still a string not a variable.
Please modify service.ts as follows:
...
return this.http.post(this.baseUrl, activity);
}
UPDATE
Another problem is your service consumes request parameters, so you have to pass these URL arguments for HTTP request in service.ts.
The valid URL should look like this:
http://localhost:8080/api/startreg?firstDate=XXX&secondDate=XXX&username=XXX
But I am not familar with TypeScript, so I don't know how to do this. Maybe you can refer to Angular2 - Http POST request parameters.
BTW, I strongly recommend that you should use #RequestBody rather than #RequestParam for your POST service.

Extracting params in traefik.frontend.auth.forward.address service

Summary
I'm trying to set up an authentication passthrough using Traefik's traefik.frontend.auth.forward.address setting. My main web service has the traefik.frontend.auth.forward.address=login.mydomain.com label on the container. Traefik seems to correctly forward incoming requests intended for mydomain.com to login.mydomain.com, but when the login form is submitted, the POST request gets turned into a GET request before it hits the login service, and the parameters of the original POST request seem to be missing. The user can never log in.
Containers
docker run -d \
-l "traefik.frontend.rule=Host:login.mydomain.com; Method:GET, POST" \
-l "traefik.enable=true" \
login-service
docker run -d \
-l "traefik.frontend.rule=Host:mydomain.com" \
-l "traefik.frontend.auth.forward.address=https://login.mydomain.com" \
-l "traefik.frontend.auth.forward.authResponseHeaders=cookie" \
-l "traefik.enable=true" \
web-service
Question
Using auth.forward.address, should I see the parameters from the original POST request in my login service? Since Traefik turns it into a GET request, where in that request should I be looking for the parameters? Or, perhaps I have misconfigured something? Missing a authResponseHeaders flag maybe?
What Works
Requests to mydomain.com show the login form from login-service, with the URL continuing to show mydomain.com; the redirect to login.mydomain.com is happening behind the scenes, which is correct.
I have also tested the login service by itself, and it seems to work. It hosts a form that submits a POST request to the service, before responding with 200 OK and a Set-Cookie header. In fact, when I go to login.mydomain.com directly, I can login, which sets my cookie, and I can go to mydomain.com and skip the login screen.
What Doesn't Work
When submitting the login form, the POST request hits the login-service (as evidenced by the logs in that service) as a GET request and the data in the POST request appears to be gone. Traefik adds an x-forwarded-method set to POST, but I can't find the data in the original POST request. I need the params from my login form to validate them, and they don't appear to be getting through to the login service.
Traefik Configuration
I don't think anything about my Traefik configuration is relevant here, but I'm including it for completeness.
checkNewVersion = true
logLevel = "DEBUG"
defaultEntryPoints = ["https","http"]
sendAnonymousUsage = true
[api]
dashboard = true
debug = true
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[retry]
[docker]
endpoint = "unix:///var/run/docker.sock"
watch = true
exposedbydefault = false
[acme]
email = "admin#mydomain.com"
storage = "acme.json"
entryPoint = "https"
OnHostRule = true
[acme.httpChallenge]
entryPoint = "http"
I tracked down Traefik's auth forward code. Sure enough, the request body is not passed downstream to the authentication service; only the headers make it that far. So much for default form submit behavior.
To get around this, I reworked my client-side authentication logic to submit a POST request with the credentials in the header instead of the body, set using XMLHttpRequest.setRequestHeader.
There's one more catch needed to make it work. I need to set cookies client-side using the Set-Cookie header returned from the authentication server, but if the server returns a 200 OK when the login is successful, Traefik will immediately pass along the original request to the user's intended destination -- meaning the Set-Cookie header will never make it to the user. What I did to get around this was return a 418 I'm a teapot when the authentication was successful. This allows the Set-Cookie header to make it back to the user's browser so the user's auth token can be set. The client then automatically reloads the intended page, this time with the correct cookie set, and now the auth server returns a 200 OK if it sees a valid cookie for the requested service.
Here's what the client side code looks like:
<form id="form" method="post" action="/">
Username: <input type="text" name="username" />
Password: <input type="password" name="password" />
<input type="submit" value="Submit" />
</form>
<script>
// Override the default form submit behavior.
// Traefik doesn't pass along body as part of proxying to the auth server,
// so the credentials have to be put in the headers instead.
const form = document.getElementById("form");
form.addEventListener('submit', function(event) {
const data = new FormData(form);
var request = new XMLHttpRequest();
request.open("POST", "/", true);
request.setRequestHeader("Auth-Form", new URLSearchParams(data).toString());
request.withCredentials = true;
request.onload = function(e) {
if (request.status == 418) {
window.location = window.location.href;
} else {
alert("Login failed.");
}
};
request.send(data);
event.preventDefault();
}, false);
</script>
I'm leaving this issue open at least until the bounty runs out because I can't imagine that this is the intended way to do this. I'm hoping someone can weigh in on how traefik.frontend.auth.forward.address is supposed to be used. Or, if someone has used another authentication proxy strategy with Traefik, I'm eager to hear about it.

Setting secure cookie for python client connection

I am currently attempting to set a secure cookie for a connection that is incoming from a Python client using Tornado, however, although setting the cookie works fine for connecting incoming from browsers, the set_secure_cookie call does not seem to work in the case of a Python client.
Below are excerpts from my Tornado server code which serves both WebSocket and HTTP Requests:
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
return self.get_secure_cookie("user")
class LoginHandler(BaseHandler):
def get(self):
self.write('<html><body><form action="/login" method="post">'
'Name: <input type="text" name="name">'
'<input type="submit" value="Sign in">'
'</form></body></html>')
def post(self):
print("post received: ", self.get_argument("name"))
try:
print('trying to set cookie')
self.set_secure_cookie("user", self.get_argument("name"))
except Exception as e:
print(e)
print("cookie: ", self.get_current_user())
self.redirect("http://192.168.6.21/")
def main():
application = tornado.web.Application([
(r'/ws', EchoWebSocket),
(r'/login', LoginHandler)
], cookie_secret="nescafeh")
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(9000)
tornado.ioloop.IOLoop.instance().start()
And attempting to send a POST request to get the cookie set on a separate client:
s=requests.Session()
r = s.post("http://127.0.0.1:9000/login", data={'name': 'hello'})
sleep(2)
print(r.text)
No errors are returned when trying to set the cookie, removing the 'self.redirect' line to see the response from the POST request does not help (there is no text printed).
Thanks a lot!
r.text is empty because you're not writing anything to the response body in your handler.
Try self.write("something") in your handler's post method and r.text should print out the response.
You can also check r.cookies to see if your cookie is set or not.