Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into feature/#196
Browse files Browse the repository at this point in the history
  • Loading branch information
Paolo Venturi committed Feb 23, 2021
2 parents 49e8e79 + 24df05f commit e4c409f
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 150 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -285,13 +285,11 @@ public Response uploadCertificate(

String encodedData = "";
DynamicCertificateState state = WAITING;
boolean available = false;
if (data != null && data.length > 0) {
encodedData = Base64.getEncoder().encodeToString(data);
available = true;
state = AVAILABLE;
}
CertificateData cert = new CertificateData(domain, "", encodedData, state, "", "", available);
CertificateData cert = new CertificateData(domain, "", encodedData, state, "", "");
cert.setManual(MANUAL.equals(certType));
cert.setDaysBeforeRenewal(daysbeforerenewal != null ? daysbeforerenewal : DEFAULT_DAYS_BEFORE_RENEWAL);
HttpProxyServer server = (HttpProxyServer) context.getAttribute("server");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,20 @@ public class CertificateData {
private DynamicCertificateState state;
private String pendingOrderLocation;
private String pendingChallengeData;
private boolean available;

// Data available at run-time only
private boolean wildcard;
private boolean manual;
private int daysBeforeRenewal;

public CertificateData(String domain, String privateKey, String chain, DynamicCertificateState state,
String orderLocation, String challengeData, boolean available) {
String orderLocation, String challengeData) {
this.domain = domain;
this.privateKey = privateKey;
this.chain = chain;
this.state = state;
this.pendingOrderLocation = orderLocation;
this.pendingChallengeData = challengeData;
this.available = available;
}

public String getDomain() {
Expand All @@ -80,10 +78,6 @@ public String getPendingChallengeData() {
return pendingChallengeData;
}

public boolean isAvailable() {
return available;
}

public void setDomain(String domain) {
this.domain = domain;
}
Expand All @@ -100,10 +94,6 @@ public void setState(DynamicCertificateState state) {
this.state = state;
}

public void setAvailable(boolean available) {
this.available = available;
}

public void setPendingOrderLocation(String orderLocation) {
this.pendingOrderLocation = orderLocation;
}
Expand Down Expand Up @@ -153,7 +143,6 @@ public int hashCode() {
hash = 89 * hash + Objects.hashCode(this.state);
hash = 89 * hash + Objects.hashCode(this.pendingOrderLocation);
hash = 89 * hash + Objects.hashCode(this.pendingChallengeData);
hash = 89 * hash + (this.available ? 1 : 0);
hash = 89 * hash + (this.manual ? 1 : 0);
hash = 89 * hash + this.daysBeforeRenewal;
return hash;
Expand All @@ -171,9 +160,6 @@ public boolean equals(Object obj) {
return false;
}
final CertificateData other = (CertificateData) obj;
if (this.available != other.available) {
return false;
}
if (this.manual != other.manual) {
return false;
}
Expand Down Expand Up @@ -203,7 +189,7 @@ public boolean equals(Object obj) {

@Override
public String toString() {
return "CertificateData{" + "domain=" + domain + ", state=" + state + ", available=" + available + ", manual=" + manual + '}';
return "CertificateData{" + "domain=" + domain + ", state=" + state + ", manual=" + manual + '}';
}

}
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
/*
Licensed to Diennea S.r.l. under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. Diennea S.r.l. licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
* Licensed to Diennea S.r.l. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Diennea S.r.l. licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.carapaceproxy.configstore;

Expand Down Expand Up @@ -85,12 +85,12 @@ public class HerdDBConfigurationStore implements ConfigurationStore {
private static final String DIGITAL_CERTIFICATES_TABLE_NAME = "digital_certificates";
private static final String CREATE_DIGITAL_CERTIFICATES_TABLE = "CREATE TABLE " + DIGITAL_CERTIFICATES_TABLE_NAME
+ "(domain string primary key, privateKey string, chain string, "
+ "state string, pendingOrder string, pendingChallenge string, available tinyint)";
+ "state string, pendingOrder string, pendingChallenge string)";
private static final String SELECT_FROM_DIGITAL_CERTIFICATES_TABLE = "SELECT * from " + DIGITAL_CERTIFICATES_TABLE_NAME + " WHERE domain=?";
private static final String UPDATE_DIGITAL_CERTIFICATES_TABLE = "UPDATE " + DIGITAL_CERTIFICATES_TABLE_NAME
+ " SET privateKey=?, chain=?, state=?, pendingOrder=?, pendingChallenge=?, available=? WHERE domain=?";
+ " SET privateKey=?, chain=?, state=?, pendingOrder=?, pendingChallenge=? WHERE domain=?";
private static final String INSERT_INTO_DIGITAL_CERTIFICATES_TABLE = "INSERT INTO " + DIGITAL_CERTIFICATES_TABLE_NAME
+ "(domain, privateKey, chain, state, pendingOrder, pendingChallenge, available) values (?, ?, ?, ?, ?, ?, ?)";
+ "(domain, privateKey, chain, state, pendingOrder, pendingChallenge) values (?, ?, ?, ?, ?, ?)";

// Table for ACME challenge tokens
private static final String ACME_CHALLENGE_TOKENS_TABLE_NAME = "acme_challenge_tokens";
Expand Down Expand Up @@ -380,15 +380,13 @@ public CertificateData loadCertificateForDomain(String domain) {
String state = rs.getString(4);
String pendingOrder = rs.getString(5);
String pendigChallenge = rs.getString(6);
boolean available = rs.getInt(7) == 1;
return new CertificateData(
domain,
privateKey,
chain,
DynamicCertificateState.fromStorableFormat(state),
pendingOrder,
pendigChallenge,
available
pendigChallenge
);
}
}
Expand All @@ -411,23 +409,20 @@ public void saveCertificate(CertificateData cert) {
String state = cert.getState().toStorableFormat();
String pendingOrder = cert.getPendingOrderLocation();
String pendigChallenge = cert.getPendingChallengeData();
int available = cert.isAvailable() ? 1 : 0;

psUpdate.setString(1, privateKey);
psUpdate.setString(2, chain);
psUpdate.setString(3, state);
psUpdate.setString(4, pendingOrder);
psUpdate.setString(5, pendigChallenge);
psUpdate.setInt(6, available);
psUpdate.setString(7, domain);
psUpdate.setString(6, domain);
if (psUpdate.executeUpdate() == 0) {
psInsert.setString(1, domain);
psInsert.setString(2, privateKey);
psInsert.setString(3, chain);
psInsert.setString(4, state);
psInsert.setString(5, pendingOrder);
psInsert.setString(6, pendigChallenge);
psInsert.setInt(7, available);
psInsert.executeUpdate();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,29 @@
import io.netty.handler.codec.http.HttpHeaderNames;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;
import org.carapaceproxy.server.cache.ContentsCache;
import org.stringtemplate.v4.NoIndentWriter;
import org.stringtemplate.v4.ST;
Expand Down Expand Up @@ -126,7 +136,65 @@ private void closeAccessLogFile() throws IOException {
os = null;
}
}

@VisibleForTesting
void rotateAccessLogFile() throws IOException {
String accesslogPath = this.currentConfiguration.getAccessLogPath();
long maxSize = this.currentConfiguration.getAccessLogMaxSize();
DateFormat date = new SimpleDateFormat("yyyy-MM-dd-ss");
String newAccessLogName = accesslogPath + "-" +date.format(new Date());

Path currentAccessLogPath = Paths.get(accesslogPath);
Path newAccessLogPath = Paths.get(newAccessLogName);
FileChannel logFileChannel = FileChannel.open(currentAccessLogPath);

try {
long currentSize = logFileChannel.size();
if(currentSize >= maxSize && maxSize > 0){
LOG.log(Level.INFO,"Maximum access log size reached. file: {0} , Size: {1} , maxSize: {2}" , new Object[]{accesslogPath,currentSize,maxSize});
Files.move(currentAccessLogPath, newAccessLogPath, StandardCopyOption.ATOMIC_MOVE);
closeAccessLogFile();
// File opening will be retried at next cycle start

//Zip old file
gzipFile(newAccessLogName, newAccessLogName+".gzip", true);
}
} catch (IOException e) {
LOG.log(Level.SEVERE , "Error: Unable to rename file {0} in {1}: " + e , new Object[]{accesslogPath, newAccessLogName});
}
}

private void gzipFile(String source_filepath, String destination_zip_filepath, boolean deleteSource) {
byte[] buffer = new byte[1024];
File source = new File(source_filepath);
File dest = new File(destination_zip_filepath);

try {
FileOutputStream fileOutputStream = new FileOutputStream(dest);
try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(fileOutputStream)) {
try (FileInputStream fileInput = new FileInputStream(source)) {
int bytes_read;

while ((bytes_read = fileInput.read(buffer)) > 0) {
gzipOutputStream.write(buffer,0, bytes_read);
}
}
gzipOutputStream.finish();
gzipOutputStream.close();

//delete uncompressed file
if(deleteSource && dest.exists()){
source.delete();
}
}
if(verbose){
LOG.log(Level.INFO, "{0} was compressed successfully", source_filepath);
}
} catch (IOException ex) {
LOG.log(Level.SEVERE, "{0} Compression failed: {1}", new Object[]{source_filepath, ex});
}
}

public void reloadConfiguration(RuntimeServerConfiguration newConfiguration) {
this.newConfiguration = newConfiguration;
}
Expand Down Expand Up @@ -249,6 +317,8 @@ public void run() {
if (System.currentTimeMillis() - lastFlush >= currentConfiguration.getAccessLogFlushInterval()) {
flushAccessLogFile();
}
//Check if is time to rotate
rotateAccessLogFile();

} catch (InterruptedException ex) {
LOG.log(Level.SEVERE, "Interrupt received");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class RuntimeServerConfiguration {
private int accessLogMaxQueueCapacity = 2000;
private int accessLogFlushInterval = 5000;
private int accessLogWaitBetweenFailures = 10000;
private long accessLogMaxSize = 524288000;
private String userRealmClassname;
private int healthProbePeriod = 0;
private int dynamicCertificatesManagerPeriod = 0;
Expand Down Expand Up @@ -130,6 +131,14 @@ public int getAccessLogWaitBetweenFailures() {
public void setAccessLogWaitBetweenFailures(int accessLogWaitBetweenFailures) {
this.accessLogWaitBetweenFailures = accessLogWaitBetweenFailures;
}

public long getAccessLogMaxSize() {
return accessLogMaxSize;
}

public void setAccessLogMaxSize(long accessLogMaxSize) {
this.accessLogMaxSize = accessLogMaxSize;
}

public String getMapperClassname() {
return mapperClassname;
Expand Down Expand Up @@ -287,6 +296,7 @@ public void configure(ConfigurationStore properties) throws ConfigurationNotVali
this.accessLogMaxQueueCapacity = properties.getInt("accesslog.queue.maxcapacity", accessLogMaxQueueCapacity);
this.accessLogFlushInterval = properties.getInt("accesslog.flush.interval", accessLogFlushInterval);
this.accessLogWaitBetweenFailures = properties.getInt("accesslog.failure.wait", accessLogWaitBetweenFailures);
this.accessLogMaxSize = properties.getLong("accesslog.maxsize", accessLogMaxSize);
String tsFormatExample;
try {
SimpleDateFormat formatter = new SimpleDateFormat(this.accessLogTimestampFormat);
Expand All @@ -300,6 +310,7 @@ public void configure(ConfigurationStore properties) throws ConfigurationNotVali
LOG.info("accesslog.queue.maxcapacity=" + accessLogMaxQueueCapacity);
LOG.info("accesslog.flush.interval=" + accessLogFlushInterval);
LOG.info("accesslog.failure.wait=" + accessLogWaitBetweenFailures);
LOG.info("accesslog.maxsize=" + accessLogMaxSize);

tryConfigureCertificates(properties);
tryConfigureListeners(properties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ private CertificateData loadOrCreateDynamicCertificateForDomain(String domain,
int daysBeforeRenewal) throws GeneralSecurityException, MalformedURLException {
CertificateData cert = store.loadCertificateForDomain(domain);
if (cert == null) {
cert = new CertificateData(domain, "", "", WAITING, "", "", false);
cert = new CertificateData(domain, "", "", WAITING, "", "");
}
cert.setWildcard(wildcard);
cert.setManual(forceManual);
Expand Down Expand Up @@ -309,7 +309,6 @@ private void certificatesLifecycle() {
PrivateKey key = loadOrCreateKeyPairForDomain(domain).getPrivate();
String chain = base64EncodeCertificateChain(certificateChain.toArray(new Certificate[0]), key);
cert.setChain(chain);
cert.setAvailable(true);
cert.setState(AVAILABLE);
notifyCertAvailChanged = true; // all other peers need to know that this cert is available.
LOG.log(Level.INFO, "Certificate issuing for domain: {0} SUCCEED. Certificate AVAILABLE.", domain);
Expand All @@ -325,7 +324,6 @@ private void certificatesLifecycle() {
}
case AVAILABLE: { // certificate saved/available/not expired
if (isCertificateExpired(base64DecodeCertificateChain(cert.getChain()), cert.getDaysBeforeRenewal())) {
cert.setAvailable(false);
cert.setState(EXPIRED);
notifyCertAvailChanged = true; // all other peers need to know that this cert is expired.
} else {
Expand Down Expand Up @@ -472,13 +470,11 @@ public void setStateOfCertificate(String id, DynamicCertificateState state) {
if (certificates.containsKey(id)) {
CertificateData cert = store.loadCertificateForDomain(id);
if (cert != null) {
boolean prevAvail = cert.isAvailable();
cert.setState(state);
cert.setAvailable(DynamicCertificateState.AVAILABLE.equals(state));
store.saveCertificate(cert);
// remember that events are not delivered to the local JVM
reloadCertificatesFromDB();
if (prevAvail != cert.isAvailable() && groupMembershipHandler != null) {
if (groupMembershipHandler != null) {
groupMembershipHandler.fireEvent(EVENT_CERT_AVAIL_CHANGED);
}
}
Expand All @@ -492,12 +488,8 @@ public void setStateOfCertificate(String id, DynamicCertificateState state) {
*/
public byte[] getCertificateForDomain(String domain) throws GeneralSecurityException {
CertificateData cert = certificates.get(domain); // certs always retrived from cache
if (cert == null) {
LOG.log(Level.SEVERE, "No dynamic certificate for domain {0}", domain);
return null;
}
if (!cert.isAvailable()) {
LOG.log(Level.SEVERE, "Dynamic certificate for domain {0} is not available: {1}", new Object[]{domain, cert});
if (cert == null || cert.getChain() == null || cert.getChain().isEmpty()) {
LOG.log(Level.SEVERE, "No dynamic certificate available for domain {0}", domain);
return null;
}
return Base64.getDecoder().decode(cert.getChain());
Expand Down
Loading

0 comments on commit e4c409f

Please sign in to comment.