Skip to content

Commit 6f53fe5

Browse files
authored
Merge pull request #167 from essentialkaos/develop
Version 3.0.0
2 parents 9f2660f + 1ea2f64 commit 6f53fe5

9 files changed

Lines changed: 189 additions & 240 deletions

File tree

.docker/alpine.docker

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ARG REGISTRY="docker.io"
44

55
## BUILDER #####################################################################
66

7-
FROM golang:alpine as builder
7+
FROM golang:alpine3.18 as builder
88

99
WORKDIR /go/src/github.com/essentialkaos/sslcli
1010

@@ -15,7 +15,7 @@ RUN apk add --no-cache git make && make deps && make all
1515

1616
## FINAL IMAGE #################################################################
1717

18-
FROM ${REGISTRY}/essentialkaos/alpine:3.15
18+
FROM ${REGISTRY}/essentialkaos/alpine:3.18
1919

2020
LABEL org.opencontainers.image.title="sslcli" \
2121
org.opencontainers.image.description="Pretty awesome command-line client for public SSLLabs API" \

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131

3232
strategy:
3333
matrix:
34-
go: [ '1.19.x', '1.20.x' ]
34+
go: [ '1.21.x', '1.22.x' ]
3535

3636
steps:
3737
- name: Checkout
@@ -61,7 +61,7 @@ jobs:
6161
- name: Set up Go
6262
uses: actions/setup-go@v5
6363
with:
64-
go-version: '1.19.x'
64+
go-version: '1.21.x'
6565

6666
- name: Download dependencies
6767
run: make deps

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212

1313
<br/>
1414

15-
`sslcli` is command-line client for <a href="https://www.ssllabs.com">SSLLabs</a> public API.
15+
`sslcli` is command-line client for <a href="https://www.ssllabs.com">Qualys SSL Labs</a> public API.
1616

17-
**IMPORTANT:** Currently, SSLLabs API doesn't provide same info as SSLLabs website.
17+
> [!CAUTION]
18+
> Currently, the SSL Labs API doesn't provide the same information as the [SSL Labs website](https://www.ssllabs.com/ssltest/).
1819
1920
### Usage demo
2021

@@ -24,7 +25,7 @@
2425

2526
#### From source
2627

27-
To build the SSLScan Client from scratch, make sure you have a working Go 1.19+ workspace ([instructions](https://go.dev/doc/install)), then:
28+
To build the SSLScan Client from scratch, make sure you have a working Go 1.21+ workspace ([instructions](https://go.dev/doc/install)), then:
2829

2930
```
3031
go install github.com/essentialkaos/sslcli@latest
@@ -71,6 +72,7 @@ Usage: sslcli {options} host…
7172
7273
Options
7374
75+
--email, -e email User account email (required)
7476
--format, -f format Output result in different formats (text/json/yaml/xml)
7577
--detailed, -d Show detailed info for each endpoint
7678
--ignore-mismatch, -i Proceed with assessments on certificate mismatch
@@ -87,6 +89,9 @@ Options
8789
8890
Examples
8991
92+
sslcli --register --email john@domain.com --org 'Some Organization' --name 'John Doe'
93+
Register new user account for scanning
94+
9095
sslcli google.com
9196
Check google.com
9297

cli/cli.go

Lines changed: 157 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import (
2020
"github.com/essentialkaos/ek/v12/fsutil"
2121
"github.com/essentialkaos/ek/v12/options"
2222
"github.com/essentialkaos/ek/v12/pager"
23+
"github.com/essentialkaos/ek/v12/req"
2324
"github.com/essentialkaos/ek/v12/strutil"
25+
"github.com/essentialkaos/ek/v12/support"
26+
"github.com/essentialkaos/ek/v12/support/deps"
2427
"github.com/essentialkaos/ek/v12/timeutil"
2528
"github.com/essentialkaos/ek/v12/usage"
2629
"github.com/essentialkaos/ek/v12/usage/completion/bash"
@@ -29,20 +32,19 @@ import (
2932
"github.com/essentialkaos/ek/v12/usage/man"
3033
"github.com/essentialkaos/ek/v12/usage/update"
3134

32-
"github.com/essentialkaos/sslscan/v13"
33-
34-
"github.com/essentialkaos/sslcli/cli/support"
35+
sslscan "github.com/essentialkaos/sslscan/v14"
3536
)
3637

3738
// ////////////////////////////////////////////////////////////////////////////////// //
3839

3940
const (
4041
APP = "SSLScan Client"
41-
VER = "2.8.0"
42+
VER = "3.0.0"
4243
DESC = "Command-line client for the SSL Labs API"
4344
)
4445

4546
const (
47+
OPT_EMAIL = "e:email"
4648
OPT_FORMAT = "f:format"
4749
OPT_DETAILED = "d:detailed"
4850
OPT_IGNORE_MISMATCH = "i:ignore-mismatch"
@@ -57,6 +59,10 @@ const (
5759
OPT_HELP = "h:help"
5860
OPT_VER = "v:version"
5961

62+
OPT_REGISTER = "register"
63+
OPT_NAME = "name"
64+
OPT_ORG = "org"
65+
6066
OPT_VERB_VER = "vv:verbose-version"
6167
OPT_COMPLETION = "completion"
6268
OPT_GENERATE_MAN = "generate-man"
@@ -94,6 +100,7 @@ type EndpointCheckInfo struct {
94100
// ////////////////////////////////////////////////////////////////////////////////// //
95101

96102
var optMap = options.Map{
103+
OPT_EMAIL: {},
97104
OPT_FORMAT: {},
98105
OPT_MAX_LEFT: {},
99106
OPT_DETAILED: {Type: options.BOOL},
@@ -108,6 +115,10 @@ var optMap = options.Map{
108115
OPT_HELP: {Type: options.BOOL},
109116
OPT_VER: {Type: options.MIXED},
110117

118+
OPT_REGISTER: {Type: options.BOOL, Bound: []string{OPT_EMAIL, OPT_NAME, OPT_ORG}},
119+
OPT_NAME: {},
120+
OPT_ORG: {},
121+
111122
OPT_VERB_VER: {Type: options.BOOL},
112123
OPT_COMPLETION: {},
113124
OPT_GENERATE_MAN: {Type: options.BOOL},
@@ -130,13 +141,17 @@ var gradeNumMap = map[string]float64{
130141
var api *sslscan.API
131142
var maxLeftToExpiry time.Duration
132143
var serverMessageShown bool
144+
var email string
133145

134146
var colorTagApp, colorTagVer string
135147

136148
// ////////////////////////////////////////////////////////////////////////////////// //
137149

138150
// Run is main function
139151
func Run(gitRev string, gomod []byte) {
152+
var err error
153+
var ok bool
154+
140155
runtime.GOMAXPROCS(2)
141156

142157
args, errs := options.Parse(optMap)
@@ -158,21 +173,32 @@ func Run(gitRev string, gomod []byte) {
158173
genAbout(gitRev).Print(options.GetS(OPT_VER))
159174
os.Exit(0)
160175
case options.GetB(OPT_VERB_VER):
161-
support.Print(APP, VER, gitRev, gomod)
176+
support.Collect(APP, VER).
177+
WithRevision(gitRev).
178+
WithDeps(deps.Extract(gomod)).
179+
WithChecks(checkAPIAvailability()).
180+
Print()
162181
os.Exit(0)
163-
case options.GetB(OPT_HELP) || len(args) == 0:
182+
case options.GetB(OPT_HELP) || (len(args) == 0 && !options.GetB(OPT_REGISTER)):
164183
genUsage().Print()
165184
os.Exit(0)
166185
}
167186

168-
err := prepare()
187+
checkForEmail()
188+
189+
err = prepare()
169190

170191
if err != nil {
171192
printError(err.Error())
172193
os.Exit(1)
173194
}
174195

175-
err, ok := process(args)
196+
switch {
197+
case options.GetB(OPT_REGISTER):
198+
err, ok = registerUser()
199+
default:
200+
err, ok = runHostCheck(args)
201+
}
176202

177203
if err != nil {
178204
printError(err.Error())
@@ -219,17 +245,80 @@ func prepare() error {
219245
return nil
220246
}
221247

222-
// process starting request processing
223-
func process(args options.Arguments) (error, bool) {
248+
// checkForEmail checks for provided email
249+
func checkForEmail() {
250+
email = strutil.Q(options.GetS(OPT_EMAIL), os.Getenv("SSLLABS_EMAIL"))
251+
252+
if email != "" {
253+
return
254+
}
255+
256+
printError("You must provide an email address to make requests to the API.")
257+
printError(
258+
"You can provide it using %s option, or using SSLLABS_EMAIL environment variable.",
259+
options.Format(OPT_EMAIL),
260+
)
261+
262+
fmtc.Println("{s-}More info: {_}https://github.com/ssllabs/ssllabs-scan/blob/master/ssllabs-api-docs-v4.md#register-for-scan-api-initiation-and-result-fetching{!}")
263+
264+
os.Exit(1)
265+
}
266+
267+
// registerUser sends user registration request
268+
func registerUser() (error, bool) {
269+
api, err := sslscan.NewAPI("SSLCli", VER, email)
270+
271+
if err != nil {
272+
if !options.GetB(OPT_FORMAT) {
273+
return fmt.Errorf("Error while sending request to SSL Labs API: %v", err), false
274+
}
275+
276+
return nil, false
277+
}
278+
279+
org := options.GetS(OPT_ORG)
280+
name := options.GetS(OPT_NAME)
281+
282+
if !strings.Contains(name, " ") {
283+
return fmt.Errorf("Name must contain first and last name"), false
284+
}
285+
286+
firstName, lastName, _ := strings.Cut(name, " ")
287+
288+
fmtc.NewLine()
289+
fmtc.Printf(" {s}Email:{!} %s\n", email)
290+
fmtc.Printf(" {s}Organization:{!} %s\n", org)
291+
fmtc.Printf(" {s}First Name:{!} %s\n", firstName)
292+
fmtc.Printf(" {s}Last Name:{!} %s\n", lastName)
293+
fmtc.NewLine()
294+
295+
resp, err := api.Register(&sslscan.RegisterRequest{
296+
FirstName: firstName,
297+
LastName: lastName,
298+
Email: email,
299+
Organization: org,
300+
})
301+
302+
if err != nil {
303+
return fmt.Errorf("Can't register user: %v", err), false
304+
}
305+
306+
fmtc.Printf("{g}%s{!}\n\n", resp.Message)
307+
308+
return nil, true
309+
}
310+
311+
// runHostCheck starts check for host
312+
func runHostCheck(args options.Arguments) (error, bool) {
224313
var ok bool
225314
var err error
226315
var hosts []string
227316

228-
api, err = sslscan.NewAPI("SSLCli", VER)
317+
api, err = sslscan.NewAPI("SSLCli", VER, email)
229318

230319
if err != nil {
231320
if !options.GetB(OPT_FORMAT) {
232-
return fmt.Errorf("Error while sending scan request to SSL Labs API: %v", err), false
321+
return fmt.Errorf("Error while sending request to SSL Labs API: %v", err), false
233322
}
234323

235324
return nil, false
@@ -602,6 +691,30 @@ func printError(f string, a ...interface{}) {
602691

603692
// ////////////////////////////////////////////////////////////////////////////////// //
604693

694+
// checkAPIAvailability checks SSLLabs API availability
695+
func checkAPIAvailability() support.Check {
696+
req.SetUserAgent("SSLCli", VER)
697+
698+
resp, err := req.Request{
699+
URL: sslscan.API_URL_INFO,
700+
AutoDiscard: true,
701+
}.Head()
702+
703+
if err != nil {
704+
return support.Check{
705+
support.CHECK_ERROR, "SSLLabs API", "Can't send request",
706+
}
707+
} else if resp.StatusCode != 200 {
708+
return support.Check{
709+
support.CHECK_ERROR, "SSLLabs API", fmt.Sprintf(
710+
"API returned non-ok status code %s", resp.StatusCode,
711+
),
712+
}
713+
}
714+
715+
return support.Check{support.CHECK_OK, "SSLLabs API", "API available"}
716+
}
717+
605718
// printCompletion prints completion for given shell
606719
func printCompletion() int {
607720
info := genUsage()
@@ -636,6 +749,7 @@ func genUsage() *usage.Info {
636749

637750
info.AppNameColorTag = colorTagApp
638751

752+
info.AddOption(OPT_EMAIL, "User account email {r}(required){!}", "email")
639753
info.AddOption(OPT_FORMAT, "Output result in different formats {s-}(text/json/yaml/xml){!}", "format")
640754
info.AddOption(OPT_DETAILED, "Show detailed info for each endpoint")
641755
info.AddOption(OPT_IGNORE_MISMATCH, "Proceed with assessments on certificate mismatch")
@@ -650,11 +764,35 @@ func genUsage() *usage.Info {
650764
info.AddOption(OPT_HELP, "Show this help message")
651765
info.AddOption(OPT_VER, "Show version")
652766

653-
info.AddExample("google.com", "Check google.com")
654-
info.AddExample("-P google.com", "Check google.com and return zero exit code only if result is perfect (A+)")
655-
info.AddExample("-p -c google.com", "Check google.com, publish results, disable cache usage")
656-
info.AddExample("-M 3m -q google.com", "Check google.com in quiet mode and return error if cert expire in 3 months")
657-
info.AddExample("hosts.txt", "Check all hosts defined in hosts.txt file")
767+
info.AddExample(
768+
"--register --email john@domain.com --org 'Some Organization' --name 'John Doe'",
769+
"Register new user account for scanning",
770+
)
771+
772+
info.AddExample(
773+
"google.com",
774+
"Check google.com",
775+
)
776+
777+
info.AddExample(
778+
"-P google.com",
779+
"Check google.com and return zero exit code only if result is perfect (A+)",
780+
)
781+
782+
info.AddExample(
783+
"-p -c google.com",
784+
"Check google.com, publish results, disable cache usage",
785+
)
786+
787+
info.AddExample(
788+
"-M 3m -q google.com",
789+
"Check google.com in quiet mode and return error if cert expire in 3 months",
790+
)
791+
792+
info.AddExample(
793+
"hosts.txt",
794+
"Check all hosts defined in hosts.txt file",
795+
)
658796

659797
return info
660798
}
@@ -672,12 +810,12 @@ func genAbout(gitRev string) *usage.About {
672810
VersionColorTag: colorTagVer,
673811
DescSeparator: "{s}—{!}",
674812

675-
License: "Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>",
676-
UpdateChecker: usage.UpdateChecker{"essentialkaos/sslcli", update.GitHubChecker},
813+
License: "Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>",
677814
}
678815

679816
if gitRev != "" {
680817
about.Build = "git:" + gitRev
818+
about.UpdateChecker = usage.UpdateChecker{"essentialkaos/sslcli", update.GitHubChecker}
681819
}
682820

683821
return about

cli/details.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"github.com/essentialkaos/ek/v12/strutil"
2121
"github.com/essentialkaos/ek/v12/timeutil"
2222

23-
"github.com/essentialkaos/sslscan/v13"
23+
sslscan "github.com/essentialkaos/sslscan/v14"
2424
)
2525

2626
// ////////////////////////////////////////////////////////////////////////////////// //
@@ -1437,7 +1437,7 @@ func isWeakSuite(suite *sslscan.Suite) bool {
14371437

14381438
// extractSubject extracts subject name from certificate subject
14391439
func extractSubject(data string) string {
1440-
subject := strutil.ReadField(data, 0, false, ",")
1440+
subject := strutil.ReadField(data, 0, false, ',')
14411441
subject = strings.ReplaceAll(subject, "CN=", "")
14421442
subject = strings.ReplaceAll(subject, "OU=", "")
14431443

0 commit comments

Comments
 (0)