This example shows how to use Akka to get SSL certificate information (i.e. public keys) from a large list of domains.
To do this, we’ll need to create a buil.sbt that pulls in Akka, a CSV reader, and a solr client (this is where we’ll store the output).
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.2.1"
libraryDependencies += "commons-net" % "commons-net" % "3.3"
libraryDependencies += "com.github.tototoshi" %% "scala-csv" % "1.3.0"
resolvers += "amateras-repo" at "http://amateras.sourceforge.jp/mvn/"
libraryDependencies += "jp.sf.amateras.solr.scala" %% "solr-scala-client" % "0.0.12"
For this script, we’ll spin up a bunch of worker actors, and a master actor that knows when the script completes. Each time an actor processes a domain, it sends a message to the master, so it can do reference counting.
To do this, we’ll need a message class (the first message goes from the orchestration function to the actors, and the second from the actors to the ‘master’ actor).
sealed trait Message
case class SSLCheck(domain: String) extends Message
case class Success() extends Message
Now we can set up the actor system:
object SSLChecker {
def main(args: Array[String]) {
val system = ActorSystem("SSLChecker")
val nrOfWorkers = 100
val sslRouter = system.actorOf(
Props(new CertificateChecker())
.withRouter(
RoundRobinRouter(nrOfInstances = nrOfWorkers)
), name = "ssl")
val f = new File(args(0))
val reader = CSVReader.open(f)
val domains = reader.iterator.map(
(x: Seq[String]) => {
try {
x(1).trim
} catch {
case e: Throwable => {
""
}
}
}
).toList
system.actorOf(
Props(
new Master(domains.length)
),
name = "master")
domains.foreach((domain) => {
sslRouter ! SSLCheck(domain)
})
}
}
The actual work is done within an actor. This gets a connection to Solr, where it writes the results:
class CertificateChecker() extends Actor {
val client = new SolrClient("http://localhost:8983/solr/ssl_certificates")
def receive = {
case SSLCheck(domain) => {
try {
val testUrl = "https://www." + domain
println(testUrl)
val url = new URL(testUrl)
val newConn = url.openConnection
newConn.setConnectTimeout(500)
newConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)");
val conn: HttpsURLConnectionImpl = newConn match {
case httpsConn: HttpsURLConnectionImpl => httpsConn
case conn => {
println(conn.getClass)
???
}
}
conn.connect()
conn.getContent
val certs = conn.getServerCertificateChain
var certIndex = 0
for (cert "root"
case 2 => "intermediate"
case 3 => "client"
case _ => "unknown"
}
client
.add(
Map(
"domain"->domain,
"level"->level,
"cipherSuite"->conn.getCipherSuite,
"hashCode"->cert.hashCode,
"publicKeyAlgorithm"->cert.getPublicKey().getAlgorithm,
"publicKeyFormat"->cert.getPublicKey().getFormat,
"signatureAlgorithmName"->cert.getSigAlgName,
"signatureAlgorithmOID"->cert.getSigAlgOID,
"issuerDN"->cert.getIssuerDN,
"serialNumber"->cert.getSerialNumber,
"subjectDN"->cert.getSubjectDN,
"subjectName"->cert.getSubjectDN.getName,
"version"->cert.getVersion,
"expirationDate"->cert.getNotAfter,
"effectiveDate"->cert.getNotBefore,
"publicKeyFormat"->cert.getPublicKey.getFormat,
"publicKeyAlgorithm"->cert.getPublicKey.getAlgorithm,
"issuerDNName"->cert.getIssuerDN.getName
))
}
client.commit
context.system.actorSelection("/user/master") ! Success()
}
catch {
case e: Throwable => {
println("Error: " + e.getMessage)
context.system.actorSelection("/user/master") ! Success()
}
}
}
case _ => {
println("Unknown message")
}
}
}
Finally, the master actor just tracks the results and shuts down on completion:
class Master(totalDomains: Int) extends Actor {
var counter = 0
def receive = {
case Success() => {
counter += 1
if(counter == totalDomains) {
context.system.shutdown
}
}
}
}