Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support HTTPS and introduce ssl_certificate policy phase #622

Merged
merged 6 commits into from
Jun 6, 2018
Merged

Conversation

mikz
Copy link
Contributor

@mikz mikz commented Feb 23, 2018

Allow APIcast to listen for HTTPS connections. Either by using static certificate or allow policy to supply one.

This PR has two parts:

Listen on HTTPS

Controlled by APICAST_HTTPS_PORT ENV variable.
Path to the certificate and key is defined by APICAST_HTTPS_CERTIFICATE and APICAST_HTTPS_CERTIFICATE_KEY ENV variables.

ssl_certificate phase

New policy phase can select a certificate. It can load it from network or a file. See lua-resty-autossl how to get letsencrypt certificates automatically.

Fixes #609

Depends on 3scale/Test-APIcast#9
Depends on 3scale/Test-APIcast#10

@mikz mikz force-pushed the listen-https branch 3 times, most recently from ab81e24 to 1df0f9a Compare June 5, 2018 12:52
@mikz mikz changed the title [wip] listen on https Support HTTPS and introduce ssl_certificate policy phase Jun 5, 2018
@@ -52,7 +52,7 @@ end
-- @tparam string phase Nginx phase
-- @treturn linked_list The context. Note: The list returned is 'read-write'.
function _M:context(phase)
if phase == 'init' then
if phase == 'init' or phase == 'ssl_certificate' then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. That is probably a leftover from a debugging session. Will take a look.

{%- assign http_port = port.apicast | default: 8080 %}
{%- assign https_port = env.APICAST_HTTPS_PORT %}

{% if http_port != https_port -%}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried this with https_port = nil ?
I remember problems when comparing nils in Liquid, but maybe it was a different issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a new test case in t/listen-https.t just to be sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So like the variable not set at all? That works - normal blackbox tests run with https_port not set.

The issue was that {% if env.somevar %} would be true even if it is empty.
This works just fine because http_port always has value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right 👍

@@ -77,6 +77,9 @@ http {
{% include tracer_conf %}
{% endif %}

ssl_session_fetch_by_lua_block { require('apicast.executor'):ssl_session_fetch() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see ssl_session_fetch() and ssl_session_store() defined anywhere. What I am missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I did not want to commit those lines just say. Will remove them. Want to keep it for the future.

@@ -14,6 +14,8 @@ local PHASES = {
'content', 'balancer',
'header_filter', 'body_filter',
'post_action', 'log', 'metrics',

'ssl_certificate',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add this new phase to the list that we have in policies.md ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep.

end

_M.rewrite = find_service
_M.ssl_certificate = find_service
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ssl_certificate executes first and needs to have access to the service so it can execute the chain

I'm going to verify the context is shared or not after removing #622 (comment). But then it still needs to find the service in both (but can skip if the service is already found).

@@ -131,8 +134,10 @@ http {

{% if https_port -%}
listen {{ https_port }} ssl;
ssl_certificate {{ env.APICAST_HTTPS_CERTIFICATE }};
ssl_certificate_key {{ env.APICAST_HTTPS_CERTIFICATE_KEY }};
ssl_certificate {{ env.APICAST_HTTPS_CERTIFICATE | default: "conf/server.crt" | filesystem | first }};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is first needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because | filesystem produces a table and it can't be serialized.

@@ -30,4 +31,8 @@ function _M:rewrite(context)
context.configuration = configuration_loader.rewrite(self.configuration, context.host)
end

function _M.ssl_certificate(_, context)
context.host = ssl.server_name()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ssl.server_name() can return an error: https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#server_name
Shouldn't we handle it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it returns error then context.host is going to be nil and no service policy chain will be found.
In that case we serve whatever certificate was set to APICAST_HTTPS_CERTIFICATE or the provided default.

Maybe we should just log the error with some low log level like debug.

@mikz mikz requested a review from a team as a code owner June 6, 2018 12:20
doc/policies.md Outdated
@@ -53,6 +53,9 @@ Notice that we did not indicate what APIcast does in the `init` and the
request. `init` is executed when APIcast boots, and `init_worker` when each
of each of its workers start.

Another phase that is not executed for every request is `ssl_certificate' because
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That ' should be a `.

listen {{ https_port }} ssl;

{%- assign https_certificate = env.APICAST_HTTPS_CERTIFICATE -%}
ssl_certificate {% if https_certificate or https_certificate != empty -%}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand this. Shouldn't it be an and instead of an or ?
Does an empty string pass the first part of the or in Liquid?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to work around an issue with the liquid library. {% "somestring" == empty %} crashes. Will remove the or part as that would still not execute when using magic tables like in other parts (where we have to check for == empty).


{%- assign https_certificate = env.APICAST_HTTPS_CERTIFICATE -%}
ssl_certificate {% if https_certificate -%}
{{ https_certificate }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one does not need | filesystem | first ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is path that was given by user in ENV, so we should not need to do any transformations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it does not really work. The | filesystem filter does not really work with absolute paths (yet).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@davidor
Copy link
Contributor

davidor commented Jun 6, 2018

👍

mikz added 6 commits June 6, 2018 16:06
* when APICAST_HTTPS_PORT is set
* APICAST_HTTPS_CERTIFICATE needs to point to a certificate
* APICAST_HTTPS_CERTIFICATE_KEY needs to point to a private key
* allows policies to serve ssl certificate
* service is detected by host from SNI
* policy should clear certificates before setting them
@mikz mikz merged commit 017d4d3 into master Jun 6, 2018
@mikz mikz deleted the listen-https branch June 6, 2018 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants