From 69f7b770b8736a4e5beb2adc0138de507e3f02ad Mon Sep 17 00:00:00 2001 From: Tim Docker Date: Fri, 29 Mar 2019 16:03:24 +1100 Subject: [PATCH] Fix nginx config generation when ssl certs don't yet exist Reviewers: barry Subscribers: #helix_omniscient Differential Revision: http://phab.helixta.com.au/D23139 --- adl/nginx.adl | 4 +- config/nginx.conf.tpl | 6 ++- src/ADL/Nginx.hs | 6 +-- src/Commands/ProxyMode/LocalState.hs | 80 ++++++++++++++++------------ 4 files changed, 56 insertions(+), 40 deletions(-) diff --git a/adl/nginx.adl b/adl/nginx.adl index a18c6a7..c535cba 100644 --- a/adl/nginx.adl +++ b/adl/nginx.adl @@ -19,8 +19,8 @@ struct NginxHttpEndPoint { }; struct NginxHttpsEndPoint { String serverNames; - String sslCertPath; - String sslCertKeyPath; + Nullable sslCertPath; + Nullable sslCertKeyPath; String letsencryptWwwDir; Nullable port; }; diff --git a/config/nginx.conf.tpl b/config/nginx.conf.tpl index 9de9e62..96fa8d4 100644 --- a/config/nginx.conf.tpl +++ b/config/nginx.conf.tpl @@ -77,7 +77,7 @@ http { listen 443 ssl; server_name {{serverNames}}; ssl_certificate {{sslCertPath}}; - ssl_certificate_key {sslCertKeyPath}}; + ssl_certificate_key {{sslCertKeyPath}}; location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -97,13 +97,15 @@ http { return 503; } } +{{#sslCertPath}} server { listen 443; server_name {{serverNames}}; ssl_certificate {{sslCertPath}}; - ssl_certificate_key {sslCertKeyPath}}; + ssl_certificate_key {{sslCertKeyPath}}; return 503; } +{{/sslCertPath}} {{/port}} {{/https}} {{/endPoints}} diff --git a/src/ADL/Nginx.hs b/src/ADL/Nginx.hs index 8e62606..bfce861 100644 --- a/src/ADL/Nginx.hs +++ b/src/ADL/Nginx.hs @@ -103,14 +103,14 @@ instance AdlValue NginxHttpEndPoint where data NginxHttpsEndPoint = NginxHttpsEndPoint { nhse_serverNames :: T.Text - , nhse_sslCertPath :: T.Text - , nhse_sslCertKeyPath :: T.Text + , nhse_sslCertPath :: ADL.Core.Nullable.Nullable (T.Text) + , nhse_sslCertKeyPath :: ADL.Core.Nullable.Nullable (T.Text) , nhse_letsencryptWwwDir :: T.Text , nhse_port :: ADL.Core.Nullable.Nullable (Data.Word.Word32) } deriving (Prelude.Eq,Prelude.Ord,Prelude.Show) -mkNginxHttpsEndPoint :: T.Text -> T.Text -> T.Text -> T.Text -> ADL.Core.Nullable.Nullable (Data.Word.Word32) -> NginxHttpsEndPoint +mkNginxHttpsEndPoint :: T.Text -> ADL.Core.Nullable.Nullable (T.Text) -> ADL.Core.Nullable.Nullable (T.Text) -> T.Text -> ADL.Core.Nullable.Nullable (Data.Word.Word32) -> NginxHttpsEndPoint mkNginxHttpsEndPoint serverNames sslCertPath sslCertKeyPath letsencryptWwwDir port = NginxHttpsEndPoint serverNames sslCertPath sslCertKeyPath letsencryptWwwDir port instance AdlValue NginxHttpsEndPoint where diff --git a/src/Commands/ProxyMode/LocalState.hs b/src/Commands/ProxyMode/LocalState.hs index c412706..6976e70 100644 --- a/src/Commands/ProxyMode/LocalState.hs +++ b/src/Commands/ProxyMode/LocalState.hs @@ -237,6 +237,8 @@ writeNginxConfig tcfg pm path eps = do let templatePath = T.unpack templatePath0 TM.automaticCompile [takeDirectory templatePath] templatePath + context <- nginxConfigContext tcfg pm eps + case etemplate of Left err -> error (show err) Right template -> do @@ -244,39 +246,51 @@ writeNginxConfig tcfg pm path eps = do text = TM.substitute template json LBS.writeFile (replaceExtension path ".ctx.json") (JS.encode json) T.writeFile path text - where - -- Build the data context to feed the mustach template - context = NginxConfContext - { ncc_healthCheck = case (tc_healthCheck tcfg, eps) of - (Just hc,(_,Just deploy):_) -> NL.fromValue (NginxHealthCheck - { nhc_incomingPath = hc_incomingPath hc - , nhc_outgoingPath = hc_outgoingPath hc - , nhc_outgoingPort = d_port deploy - }) - _ -> NL.null - , ncc_endPoints = fmap contextEndPoint eps - } - contextEndPoint (ep@EndPoint{ep_etype=Ep_httpOnly},deploy) = Ne_http - NginxHttpEndPoint - { nhe_serverNames = T.intercalate " " (ep_serverNames ep) - , nhe_port = NL.fromMaybe (fmap d_port deploy) - } - contextEndPoint (ep@EndPoint{ep_etype=Ep_httpsWithRedirect certMode},deploy) = Ne_https - NginxHttpsEndPoint - { nhse_serverNames = T.intercalate " " (ep_serverNames ep) - , nhse_port = NL.fromMaybe (fmap d_port deploy) - , nhse_sslCertPath = sslCertPath certMode - , nhse_sslCertKeyPath = sslCertKeyPath certMode - , nhse_letsencryptWwwDir = tc_letsencryptWwwDir tcfg - } - - sslCertPath :: SslCertMode -> T.Text - sslCertPath (Scm_explicit scp) = scp_sslCertificate scp - sslCertPath Scm_generated = tc_letsencryptPrefixDir tcfg <> "/etc/letsencrypt/live/" <> (tc_autoCertName tcfg) <> "/fullchain.pem" - - sslCertKeyPath :: SslCertMode -> T.Text - sslCertKeyPath (Scm_explicit scp) = scp_sslCertificateKey scp - sslCertKeyPath Scm_generated = tc_letsencryptPrefixDir tcfg <> "/etc/letsencrypt/live/" <> (tc_autoCertName tcfg) <>"/privkey.pem"; + +-- build up the context to be injected into the nginx config template +nginxConfigContext:: ToolConfig -> ProxyModeConfig -> [(EndPoint,Maybe Deploy)] -> IO NginxConfContext +nginxConfigContext tcfg pm eps = do + -- We only include the ssl paths for the generated certificate in + -- the context if the physical paths exist on disk + genSslCertPath <- existingFilePath (tc_letsencryptPrefixDir tcfg <> "/etc/letsencrypt/live/" <> (tc_autoCertName tcfg) <> "/fullchain.pem") + genSslCertKeyPath <- existingFilePath (tc_letsencryptPrefixDir tcfg <> "/etc/letsencrypt/live/" <> (tc_autoCertName tcfg) <>"/privkey.pem") + let context = NginxConfContext + { ncc_healthCheck = case (tc_healthCheck tcfg, eps) of + (Just hc,(_,Just deploy):_) -> NL.fromValue (NginxHealthCheck + { nhc_incomingPath = hc_incomingPath hc + , nhc_outgoingPath = hc_outgoingPath hc + , nhc_outgoingPort = d_port deploy + }) + _ -> NL.null + , ncc_endPoints = fmap contextEndPoint eps + } + contextEndPoint (ep@EndPoint{ep_etype=Ep_httpOnly},deploy) = Ne_http + NginxHttpEndPoint + { nhe_serverNames = T.intercalate " " (ep_serverNames ep) + , nhe_port = NL.fromMaybe (fmap d_port deploy) + } + contextEndPoint (ep@EndPoint{ep_etype=Ep_httpsWithRedirect certMode},deploy) = Ne_https + NginxHttpsEndPoint + { nhse_serverNames = T.intercalate " " (ep_serverNames ep) + , nhse_port = NL.fromMaybe (fmap d_port deploy) + , nhse_sslCertPath = sslCertPath certMode + , nhse_sslCertKeyPath = sslCertKeyPath certMode + , nhse_letsencryptWwwDir = tc_letsencryptWwwDir tcfg + } + sslCertPath :: SslCertMode -> NL.Nullable T.Text + sslCertPath (Scm_explicit scp) = NL.fromValue (scp_sslCertificate scp) + sslCertPath Scm_generated = genSslCertPath + + sslCertKeyPath :: SslCertMode -> NL.Nullable T.Text + sslCertKeyPath (Scm_explicit scp) = NL.fromValue (scp_sslCertificateKey scp) + sslCertKeyPath Scm_generated = genSslCertKeyPath + + return context + +existingFilePath :: T.Text -> IO (NL.Nullable T.Text) +existingFilePath path = do + exists <- doesFileExist (T.unpack path) + return (if exists then (NL.fromValue path) else NL.null) -- Extend the release template context with port variables, -- which include the http port where the deploy will make