diff options
Diffstat (limited to 'src/main/java/org/openslx/taskmanager/tasks/LighttpdHttps.java')
-rw-r--r-- | src/main/java/org/openslx/taskmanager/tasks/LighttpdHttps.java | 245 |
1 files changed, 204 insertions, 41 deletions
diff --git a/src/main/java/org/openslx/taskmanager/tasks/LighttpdHttps.java b/src/main/java/org/openslx/taskmanager/tasks/LighttpdHttps.java index 08fac2a..20ef463 100644 --- a/src/main/java/org/openslx/taskmanager/tasks/LighttpdHttps.java +++ b/src/main/java/org/openslx/taskmanager/tasks/LighttpdHttps.java @@ -3,9 +3,11 @@ package org.openslx.taskmanager.tasks; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.openslx.satserver.util.Exec; +import org.openslx.satserver.util.Exec.ExecCallback; import org.openslx.satserver.util.Util; import org.openslx.taskmanager.api.AbstractTask; @@ -19,6 +21,8 @@ public class LighttpdHttps extends AbstractTask { private Output status = new Output(); + + // --- User-supplied cert --- @Expose private String importcert = null; @@ -26,16 +30,43 @@ public class LighttpdHttps extends AbstractTask private String importkey = null; @Expose private String importchain = null; - + + // --- Let's encrypt or similar (ACME) --- + + @Expose + private String acmeMode = null; + @Expose + private String acmeMail = null; + @Expose + private String[] acmeDomains = null; + @Expose + private String acmeProvider = null; + @Expose + private boolean acmeWipeAll = false; + @Expose + private String acmeKeyId = null; + @Expose + private String acmeHmacKey = null; + + // ---- Self-signed ---- + + /** IP address to put in self-signed cert */ @Expose private String proxyip = null; - + + // ------ Force HTTPS? ------ + + /** Enable redirect from HTTP to HTTPS? */ @Expose private boolean redirect; + /** Only set the requested redirect mode, nothing else */ @Expose private boolean redirectOnly; + + // ------- - private List<String> baseCmd = Arrays.asList( new String[] { "sudo", "-n", "-u", "root", "/opt/taskmanager/scripts/install-https" } ); + private static List<String> BASE_CMD = Collections.unmodifiableList( Arrays.asList( new String[] { + "sudo", "-n", "-u", "root", "/opt/taskmanager/scripts/install-https" } ) ); @Override protected boolean initTask() @@ -51,14 +82,19 @@ public class LighttpdHttps extends AbstractTask return setRedirect(); if ( this.importcert != null && this.importkey != null && !this.importcert.isEmpty() && !this.importkey.isEmpty() ) return createFromInput(); + if ( this.acmeMode != null ) + return handleAcme(); if ( this.proxyip != null && !this.proxyip.isEmpty() ) return createRandom(); return disableHttps(); } + /** + * Create a new self-signed certificate and use that. + */ private boolean createRandom() { - List<String> cmd = new ArrayList<>( baseCmd ); + List<String> cmd = new ArrayList<>( BASE_CMD ); if ( this.redirect ) { cmd.add( "--redirect" ); } @@ -66,19 +102,21 @@ public class LighttpdHttps extends AbstractTask cmd.add( this.proxyip ); int ret = Exec.sync( 45, cmd.toArray( new String[ cmd.size() ] ) ); if ( ret != 0 ) { - status.error = "generator exited with code " + ret; + status.processStdOut( "generator exited with code " + ret ); return false; } return true; } + /** + * Create HTTPS config from cert/key/chain provided in task data. + */ private boolean createFromInput() { // Import supplied certificate and key. Test if they are valid first File tmpKey = null; File tmpCert = null; File tmpChain = null; - List<String> cmd; try { try { tmpCert = File.createTempFile( "bwlp-", ".pem" ); @@ -90,53 +128,163 @@ public class LighttpdHttps extends AbstractTask Util.writeStringToFile( tmpChain, this.importchain ); } } catch ( Exception e ) { - status.error = "Could not create temporary files: " + e.getMessage(); + status.processStdOut( "Could not create temporary files: " + e.getMessage() ); return false; } - int ret; - cmd = new ArrayList<>( baseCmd ); - cmd.add( "--test" ); - cmd.add( tmpKey.getAbsolutePath() ); - cmd.add( tmpCert.getAbsolutePath() ); - ret = Exec.sync( 45, cmd.toArray( new String[ cmd.size() ] ) ); - if ( ret != 0 ) { - status.error = "Given key and certificate do not match, or have invalid format (exit code: " + ret + ")"; - return false; + return createFromFiles( tmpKey, tmpCert, tmpChain ); + } finally { + if ( tmpKey != null ) { + tmpKey.delete(); } - cmd = new ArrayList<>( baseCmd ); - if ( this.redirect ) { - cmd.add( "--redirect" ); + if ( tmpCert != null ) { + tmpCert.delete(); } - cmd.add( "--import" ); - cmd.add( tmpKey.getAbsolutePath() ); - cmd.add( tmpCert.getAbsolutePath() ); if ( tmpChain != null ) { - cmd.add( tmpChain.getAbsolutePath() ); + tmpChain.delete(); } - ret = Exec.sync( 45, cmd.toArray( new String[ cmd.size() ] ) ); - if ( ret != 0 ) { - status.error = "import exited with code " + ret; - return false; - } - return true; - } finally { - if ( tmpKey != null ) - tmpKey.delete(); - if ( tmpCert != null ) - tmpCert.delete(); } } + + /** + * Call deployment script, passing along predefined key/cert/chain files. + */ + private boolean createFromFiles( File tmpKey, File tmpCert, File tmpChain ) + { + List<String> cmd; + int ret; + + cmd = new ArrayList<>( BASE_CMD ); + cmd.add( "--test" ); + cmd.add( tmpKey.getAbsolutePath() ); + cmd.add( tmpCert.getAbsolutePath() ); + ret = Exec.sync( 45, status, cmd.toArray( new String[ cmd.size() ] ) ); + if ( ret != 0 ) { + status.processStdOut( "Given key and certificate do not match, or have invalid format (exit code: " + ret + ")" ); + return false; + } + cmd = new ArrayList<>( BASE_CMD ); + if ( this.redirect ) { + cmd.add( "--redirect" ); + } + cmd.add( "--import" ); + cmd.add( tmpKey.getAbsolutePath() ); + cmd.add( tmpCert.getAbsolutePath() ); + if ( tmpChain != null ) { + cmd.add( tmpChain.getAbsolutePath() ); + } + ret = Exec.sync( 45, status, cmd.toArray( new String[ cmd.size() ] ) ); + if ( ret != 0 ) { + status.processStdOut( "import exited with code " + ret ); + return false; + } + return true; + } + + private boolean handleAcme() + { + if ( this.acmeMode.equals( "issue" ) ) { + return createWithAcme(); + } + if ( this.acmeMode.equals( "renew" ) ) { + return checkRenewAcme(); + } + if ( this.acmeMode.equals( "try-enable" ) ) { + return tryEnableAcme(); + } + status.processStdOut( "Invalid ACME mode: " + this.acmeMode ); + return false; + } + + /** + * Create a brand new certificate via ACME + */ + private boolean createWithAcme() + { + if ( Util.isEmpty( this.acmeMail ) ) { + status.processStdOut( "Invalid E-Mail provided" ); + return false; + } + if ( this.acmeDomains == null || this.acmeDomains.length == 0 ) { + status.processStdOut( "No domains provided" ); + return false; + } + List<String> cmd = new ArrayList<>( BASE_CMD ); + if ( this.redirect ) { + cmd.add( "--redirect" ); + } + if ( this.acmeWipeAll ) { + cmd.add( "--acme-wipe" ); + } + if ( this.acmeKeyId != null ) { + cmd.add( "--acme-key-id" ); + cmd.add( this.acmeKeyId ); + } + if ( this.acmeHmacKey != null ) { + cmd.add( "--acme-hmac-key" ); + cmd.add( this.acmeHmacKey ); + } + cmd.add( "--acme-issue" ); + cmd.add( this.acmeProvider ); + cmd.add( this.acmeMail ); + for ( String d : this.acmeDomains ) { + cmd.add( d ); + } + int ret = Exec.sync( 120, status, cmd.toArray( new String[ cmd.size() ] ) ); + if ( ret != 0 ) { + status.processStdOut( "acme issue exited with code " + ret ); + return false; + } + return true; + } + + /** + * Trigger a renew check and according renew if required. + */ + private boolean checkRenewAcme() + { + if ( this.acmeDomains == null || this.acmeDomains.length == 0 ) { + status.processStdOut( "No domain provided" ); + return false; + } + List<String> cmd = new ArrayList<>( BASE_CMD ); + if ( this.redirect ) { + cmd.add( "--redirect" ); + } + cmd.add( "--acme-renew" ); + cmd.add( this.acmeDomains[0] ); // Only needs primary domain to find config + int ret = Exec.sync( 120, status, cmd.toArray( new String[ cmd.size() ] ) ); + if ( ret != 0 ) { + status.processStdOut( "acme renew exited with code " + ret ); + return false; + } + return true; + } + + private boolean tryEnableAcme() + { + List<String> cmd = new ArrayList<>( BASE_CMD ); + if ( this.redirect ) { + cmd.add( "--redirect" ); + } + cmd.add( "--acme-try-enable" ); + int ret = Exec.sync( 10, status, cmd.toArray( new String[ cmd.size() ] ) ); + if ( ret != 0 ) { + status.processStdOut( "acme try-enable exited with code " + ret ); + return false; + } + return true; + } private boolean setRedirect() { - List<String> cmd = new ArrayList<>( baseCmd ); + List<String> cmd = new ArrayList<>( BASE_CMD ); cmd.add( "--redirect-only" ); if ( this.redirect ) { cmd.add( "--redirect" ); } int ret = Exec.sync( 10, cmd.toArray( new String[ cmd.size() ] ) ); if ( ret != 0 ) { - status.error = "set redirect exited with code " + ret; + status.processStdOut( "set redirect exited with code " + ret ); return false; } return true; @@ -144,11 +292,11 @@ public class LighttpdHttps extends AbstractTask private boolean disableHttps() { - List<String> cmd = new ArrayList<>( baseCmd ); + List<String> cmd = new ArrayList<>( BASE_CMD ); cmd.add( "--disable" ); int ret = Exec.sync( 10, cmd.toArray( new String[ cmd.size() ] ) ); if ( ret != 0 ) { - status.error = "disable exited with code " + ret; + status.processStdOut( "disable exited with code " + ret ); return false; } return true; @@ -157,10 +305,25 @@ public class LighttpdHttps extends AbstractTask /** * Output - contains additional status data of this task */ - @SuppressWarnings( "unused" ) - private static class Output + private static class Output implements ExecCallback { - protected String error = null; + protected StringBuilder error; + + @Override + public void processStdOut( String line ) + { + if ( error == null ) { + error = new StringBuilder(); + } + error.append( line ); + error.append( '\n' ); + } + + @Override + public void processStdErr( String line ) + { + processStdOut( line ); + } } } |