Skip to content

Commit 6d01c54

Browse files
committed
ssllabs as a library v3
1 parent cabfb5e commit 6d01c54

File tree

2 files changed

+304
-183
lines changed

2 files changed

+304
-183
lines changed
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"flag"
7+
"fmt"
8+
"log"
9+
"net"
10+
"net/url"
11+
"os"
12+
"sort"
13+
"strconv"
14+
"strings"
15+
16+
scan "github.com/prasincs/ssllabs-scan"
17+
)
18+
19+
var USER_AGENT = "ssllabs-scan v1.4.0 (stable $Id$)"
20+
21+
var logLevel = scan.LOG_NOTICE
22+
23+
func flattenJSON(inputJSON map[string]interface{}, rootKey string, flattened *map[string]interface{}) {
24+
var keysep = "." // Char to separate keys
25+
var Q = "\"" // Char to envelope strings
26+
27+
for rkey, value := range inputJSON {
28+
key := rootKey + rkey
29+
if _, ok := value.(string); ok {
30+
(*flattened)[key] = Q + value.(string) + Q
31+
} else if _, ok := value.(float64); ok {
32+
(*flattened)[key] = fmt.Sprintf("%.f", value)
33+
} else if _, ok := value.(bool); ok {
34+
(*flattened)[key] = value.(bool)
35+
} else if _, ok := value.([]interface{}); ok {
36+
for i := 0; i < len(value.([]interface{})); i++ {
37+
aKey := key + keysep + strconv.Itoa(i)
38+
if _, ok := value.([]interface{})[i].(string); ok {
39+
(*flattened)[aKey] = Q + value.([]interface{})[i].(string) + Q
40+
} else if _, ok := value.([]interface{})[i].(float64); ok {
41+
(*flattened)[aKey] = value.([]interface{})[i].(float64)
42+
} else if _, ok := value.([]interface{})[i].(bool); ok {
43+
(*flattened)[aKey] = value.([]interface{})[i].(bool)
44+
} else {
45+
flattenJSON(value.([]interface{})[i].(map[string]interface{}), key+keysep+strconv.Itoa(i)+keysep, flattened)
46+
}
47+
}
48+
} else if value == nil {
49+
(*flattened)[key] = nil
50+
} else {
51+
flattenJSON(value.(map[string]interface{}), key+keysep, flattened)
52+
}
53+
}
54+
}
55+
56+
func flattenAndFormatJSON(inputJSON []byte) *[]string {
57+
var flattened = make(map[string]interface{})
58+
59+
mappedJSON := map[string]interface{}{}
60+
err := json.Unmarshal(inputJSON, &mappedJSON)
61+
if err != nil {
62+
log.Fatalf("[ERROR] Reconstitution of JSON failed: %v", err)
63+
}
64+
65+
// Flatten the JSON structure, recursively
66+
flattenJSON(mappedJSON, "", &flattened)
67+
68+
// Make a sorted index, so we can print keys in order
69+
kIndex := make([]string, len(flattened))
70+
ki := 0
71+
for key, _ := range flattened {
72+
kIndex[ki] = key
73+
ki++
74+
}
75+
sort.Strings(kIndex)
76+
77+
// Ordered flattened data
78+
var flatStrings []string
79+
for _, value := range kIndex {
80+
flatStrings = append(flatStrings, fmt.Sprintf("\"%v\": %v\n", value, flattened[value]))
81+
}
82+
return &flatStrings
83+
}
84+
85+
func readLines(path *string) ([]string, error) {
86+
file, err := os.Open(*path)
87+
if err != nil {
88+
return nil, err
89+
}
90+
defer file.Close()
91+
92+
var lines []string
93+
scanner := bufio.NewScanner(file)
94+
for scanner.Scan() {
95+
var line = strings.TrimSpace(scanner.Text())
96+
if (!strings.HasPrefix(line, "#")) && (line != "") {
97+
lines = append(lines, line)
98+
}
99+
}
100+
return lines, scanner.Err()
101+
}
102+
103+
func validateURL(URL string) bool {
104+
_, err := url.Parse(URL)
105+
if err != nil {
106+
return false
107+
} else {
108+
return true
109+
}
110+
}
111+
112+
func validateHostname(hostname string) bool {
113+
addrs, err := net.LookupHost(hostname)
114+
115+
// In some cases there is no error
116+
// but there are also no addresses
117+
if err != nil || len(addrs) < 1 {
118+
return false
119+
} else {
120+
return true
121+
}
122+
}
123+
124+
func main() {
125+
var conf_api = flag.String("api", "BUILTIN", "API entry point, for example https://www.example.com/api/")
126+
var conf_grade = flag.Bool("grade", false, "Output only the hostname: grade")
127+
var conf_hostcheck = flag.Bool("hostcheck", false, "If true, host resolution failure will result in a fatal error.")
128+
var conf_hostfile = flag.String("hostfile", "", "File containing hosts to scan (one per line)")
129+
var conf_ignore_mismatch = flag.Bool("ignore-mismatch", false, "If true, certificate hostname mismatch does not stop assessment.")
130+
var conf_insecure = flag.Bool("insecure", false, "Skip certificate validation. For use in development only. Do not use.")
131+
var conf_json_flat = flag.Bool("json-flat", false, "Output results in flattened JSON format")
132+
var conf_quiet = flag.Bool("quiet", false, "Disable status messages (logging)")
133+
var conf_usecache = flag.Bool("usecache", false, "If true, accept cached results (if available), else force live scan.")
134+
var conf_maxage = flag.Int("maxage", 0, "Maximum acceptable age of cached results, in hours. A zero value is ignored.")
135+
var conf_verbosity = flag.String("verbosity", "info", "Configure log verbosity: error, notice, info, debug, or trace.")
136+
var conf_version = flag.Bool("version", false, "Print version and API location information and exit")
137+
138+
flag.Parse()
139+
140+
if *conf_version {
141+
fmt.Println(USER_AGENT)
142+
fmt.Println("API location: " + *conf_api)
143+
return
144+
}
145+
146+
scan.IgnoreMismatch(*conf_ignore_mismatch)
147+
148+
if *conf_quiet {
149+
logLevel = scan.LOG_NONE
150+
} else {
151+
logLevel = scan.ParseLogLevel(strings.ToLower(*conf_verbosity))
152+
}
153+
154+
// We prefer cached results
155+
scan.UseCache(*conf_usecache)
156+
157+
if *conf_maxage != 0 {
158+
scan.SetMaxAge(*conf_maxage)
159+
}
160+
161+
// Verify that the API entry point is a URL.
162+
if *conf_api != "BUILTIN" {
163+
if validateURL(*conf_api) == false {
164+
log.Fatalf("[ERROR] Invalid API URL: %v", *conf_api)
165+
}
166+
scan.SetAPILocation(*conf_api)
167+
}
168+
169+
var hostnames []string
170+
171+
if *conf_hostfile != "" {
172+
// Open file, and read it
173+
var err error
174+
hostnames, err = readLines(conf_hostfile)
175+
if err != nil {
176+
log.Fatalf("[ERROR] Reading from specified hostfile failed: %v", err)
177+
}
178+
179+
} else {
180+
// Read hostnames from the rest of the args
181+
hostnames = flag.Args()
182+
}
183+
184+
if *conf_hostcheck {
185+
// Validate all hostnames before we attempt to test them. At least
186+
// one hostname is required.
187+
for _, host := range hostnames {
188+
if validateHostname(host) == false {
189+
log.Fatalf("[ERROR] Invalid hostname: %v", host)
190+
}
191+
}
192+
}
193+
194+
scan.AllowInsecure(*conf_insecure)
195+
196+
hp := scan.NewHostProvider(hostnames)
197+
manager := scan.NewManager(hp)
198+
199+
// Respond to events until all the work is done.
200+
for {
201+
_, running := <-manager.FrontendEventChannel
202+
if running == false {
203+
var err error
204+
205+
if hp.StartingLen == 0 {
206+
return
207+
}
208+
209+
if *conf_grade {
210+
for i := range manager.Results.Responses {
211+
results := []byte(manager.Results.Responses[i])
212+
213+
// Fill LabsReport with json response received i.e results
214+
var labsReport scan.LabsReport
215+
err = json.Unmarshal(results, &labsReport)
216+
// Check for error while unmarshalling. If yes then display error messsage and terminate the program
217+
if err != nil {
218+
log.Fatalf("[ERROR] JSON unmarshal error: %v", err)
219+
}
220+
221+
// Printing the Hostname and IpAddress with grades
222+
fmt.Println()
223+
if !strings.EqualFold(labsReport.StatusMessage, "ERROR") {
224+
fmt.Printf("HostName:\"%v\"\n", labsReport.Host)
225+
for _, endpoints := range labsReport.Endpoints {
226+
if endpoints.FutureGrade != "" {
227+
fmt.Printf("\"%v\":\"%v\"->\"%v\"\n", endpoints.IpAddress, endpoints.Grade, endpoints.FutureGrade)
228+
} else {
229+
if endpoints.Grade != "" {
230+
fmt.Printf("\"%v\":\"%v\"\n", endpoints.IpAddress, endpoints.Grade)
231+
} else {
232+
// When no grade is seen print Status Message
233+
fmt.Printf("\"%v\":\"%v\"\n", endpoints.IpAddress, endpoints.StatusMessage)
234+
}
235+
}
236+
}
237+
}
238+
}
239+
} else if *conf_json_flat {
240+
// Flat JSON and RAW
241+
242+
for i := range manager.Results.Responses {
243+
results := []byte(manager.Results.Responses[i])
244+
245+
flattened := flattenAndFormatJSON(results)
246+
247+
// Print the flattened data
248+
fmt.Println(*flattened)
249+
}
250+
} else {
251+
// Raw (non-Go-mangled) JSON output
252+
253+
fmt.Println("[")
254+
for i := range manager.Results.Responses {
255+
results := manager.Results.Responses[i]
256+
257+
if i > 0 {
258+
fmt.Println(",")
259+
}
260+
fmt.Println(results)
261+
262+
}
263+
fmt.Println("]")
264+
}
265+
266+
if err != nil {
267+
log.Fatalf("[ERROR] Output to JSON failed: %v", err)
268+
}
269+
270+
if logLevel >= scan.LOG_INFO {
271+
log.Println("[INFO] All assessments complete; shutting down")
272+
}
273+
274+
return
275+
}
276+
}
277+
}

0 commit comments

Comments
 (0)