Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Simple `kolide_cryptsetup_status` as a wrapper over the `cryptsetup status` command
- Loading branch information
1 parent
d4b2df8
commit 2b6f90e
Showing
12 changed files
with
366 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package cryptsetup | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/davecgh/go-spew/spew" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// parseStatus parses the output from `cryptsetup status`. This is a | ||
// pretty simple key, value format, but does have a free form first | ||
// line. It's not clear if this is going to be stable, or change | ||
// across versions. | ||
func parseStatus(rawdata []byte) (map[string]interface{}, error) { | ||
var data map[string]interface{} | ||
|
||
if len(rawdata) == 0 { | ||
return nil, errors.New("No data") | ||
} | ||
|
||
scanner := bufio.NewScanner(bytes.NewReader(rawdata)) | ||
firstLine := true | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
if firstLine { | ||
var err error | ||
data, err = parseFirstLine(line) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
firstLine = false | ||
continue | ||
} | ||
|
||
kv := strings.SplitN(line, ": ", 2) | ||
|
||
// blank lines, or other unexpected input can just be skipped. | ||
if len(kv) < 2 { | ||
continue | ||
} | ||
|
||
data[strings.ReplaceAll(strings.TrimSpace(kv[0]), " ", "_")] = strings.TrimSpace(kv[1]) | ||
} | ||
|
||
return data, nil | ||
} | ||
|
||
// regexp for the first line of the status output. | ||
var firstLineRegexp = regexp.MustCompile(`^(?:Device (.*) (not found))|(?:(.*?) is ([a-z]+)(?:\.| and is (in use)))`) | ||
|
||
// parseFirstLine parses the first line of the status output. This | ||
// appears to be a free form string indicating several pieces of | ||
// information. It is parsed with a single regexp. (See tests for | ||
// examples) | ||
func parseFirstLine(line string) (map[string]interface{}, error) { | ||
if line == "" { | ||
return nil, errors.Errorf("Invalid first line") | ||
} | ||
|
||
m := firstLineRegexp.FindAllStringSubmatch(line, -1) | ||
if len(m) != 1 { | ||
return nil, errors.Errorf("Failed to match first line: %s", line) | ||
} | ||
if len(m[0]) != 6 { | ||
spew.Dump(m) | ||
return nil, errors.Errorf("Got %d matches. Expected 6. Failed to match first line: %s", len(m[0]), line) | ||
} | ||
|
||
data := make(map[string]interface{}, 3) | ||
|
||
// check for $1 and $2 for the error condition | ||
if m[0][1] != "" && m[0][2] != "" { | ||
data["short_name"] = m[0][1] | ||
data["status"] = strings.ReplaceAll(m[0][2], " ", "_") | ||
data["mounted"] = strconv.FormatBool(false) | ||
return data, nil | ||
} | ||
|
||
if m[0][3] != "" && m[0][4] != "" { | ||
data["display_name"] = m[0][3] | ||
data["status"] = strings.ReplaceAll(m[0][4], " ", "_") | ||
data["mounted"] = strconv.FormatBool(m[0][5] != "") | ||
return data, nil | ||
} | ||
|
||
return nil, errors.Errorf("Unknown first line: %s", line) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package cryptsetup | ||
|
||
import ( | ||
"io/ioutil" | ||
"path/filepath" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestParseStatusErrors(t *testing.T) { | ||
t.Parallel() | ||
|
||
var tests = []struct { | ||
input string | ||
}{ | ||
{ | ||
input: "", | ||
}, | ||
{ | ||
input: "\n\n\n\n", | ||
}, | ||
{ | ||
input: "type: LUKS2", | ||
}, | ||
{ | ||
input: "Hello world", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run("", func(t *testing.T) { | ||
data, err := parseStatus([]byte(tt.input)) | ||
assert.Error(t, err, "parseStatus") | ||
assert.Nil(t, data, "data is nil") | ||
|
||
}) | ||
} | ||
|
||
} | ||
func TestParseStatus(t *testing.T) { | ||
t.Parallel() | ||
|
||
var tests = []struct { | ||
infile string | ||
len int | ||
status string | ||
mounted bool | ||
ctype string | ||
keysize string | ||
key_location string | ||
}{ | ||
{ | ||
infile: "status-active-luks1.txt", | ||
status: "active", | ||
mounted: true, | ||
ctype: "LUKS1", | ||
keysize: "512 bits", | ||
key_location: "dm-crypt", | ||
}, | ||
{ | ||
infile: "status-active-luks2.txt", | ||
status: "active", | ||
mounted: true, | ||
ctype: "LUKS2", | ||
keysize: "512 bits", | ||
key_location: "keyring", | ||
}, | ||
{ | ||
infile: "status-active-mounted.txt", | ||
status: "active", | ||
mounted: true, | ||
ctype: "PLAIN", | ||
keysize: "256 bits", | ||
key_location: "dm-crypt", | ||
}, | ||
{ | ||
infile: "status-active-umounted.txt", | ||
status: "active", | ||
ctype: "PLAIN", | ||
keysize: "256 bits", | ||
key_location: "dm-crypt", | ||
}, | ||
{ | ||
infile: "status-active.txt", | ||
status: "active", | ||
mounted: true, | ||
ctype: "PLAIN", | ||
keysize: "256 bits", | ||
key_location: "dm-crypt", | ||
}, | ||
{ | ||
infile: "status-error.txt", | ||
status: "not_found", | ||
}, | ||
{ | ||
infile: "status-inactive.txt", | ||
status: "inactive", | ||
}, | ||
{ | ||
infile: "status-unactive.txt", | ||
status: "inactive", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.infile, func(t *testing.T) { | ||
input, err := ioutil.ReadFile(filepath.Join("testdata", tt.infile)) | ||
require.NoError(t, err, "read input file") | ||
|
||
data, err := parseStatus(input) | ||
require.NoError(t, err, "parseStatus") | ||
|
||
assert.Equal(t, tt.status, data["status"], "status") | ||
assert.Equal(t, strconv.FormatBool(tt.mounted), data["mounted"], "mounted") | ||
|
||
// These values aren't populated in the map, | ||
// so only check them if the test case lists | ||
// them | ||
if tt.ctype != "" { | ||
assert.Equal(t, tt.ctype, data["type"], "type") | ||
assert.Equal(t, tt.keysize, data["keysize"], "keysize") | ||
assert.Equal(t, tt.key_location, data["key_location"], "key_location") | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package cryptsetup | ||
|
||
import ( | ||
"context" | ||
"strings" | ||
|
||
"github.com/go-kit/kit/log" | ||
"github.com/go-kit/kit/log/level" | ||
"github.com/kolide/launcher/pkg/dataflatten" | ||
"github.com/kolide/launcher/pkg/osquery/tables/dataflattentable" | ||
"github.com/kolide/launcher/pkg/osquery/tables/tablehelpers" | ||
"github.com/kolide/osquery-go" | ||
"github.com/kolide/osquery-go/plugin/table" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
const cryptsetupPath = "/usr/sbin/cryptsetup" | ||
|
||
const allowedNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/_" | ||
|
||
type Table struct { | ||
client *osquery.ExtensionManagerClient | ||
logger log.Logger | ||
name string | ||
} | ||
|
||
func TablePlugin(client *osquery.ExtensionManagerClient, logger log.Logger) *table.Plugin { | ||
columns := dataflattentable.Columns( | ||
table.TextColumn("name"), | ||
) | ||
|
||
t := &Table{ | ||
client: client, | ||
logger: logger, | ||
name: "kolide_cryptsetup_status", | ||
} | ||
|
||
return table.NewPlugin(t.name, columns, t.generate) | ||
} | ||
|
||
func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { | ||
var results []map[string]string | ||
|
||
requestedNames := tablehelpers.GetConstraints(queryContext, "name", | ||
tablehelpers.WithAllowedCharacters(allowedNameCharacters), | ||
tablehelpers.WithLogger(t.logger), | ||
) | ||
|
||
if len(requestedNames) == 0 { | ||
return results, errors.Errorf("The %s table requires that you specify a constraint for name", t.name) | ||
} | ||
|
||
for _, name := range requestedNames { | ||
output, err := tablehelpers.Exec(ctx, t.logger, 15, []string{cryptsetupPath}, []string{"--readonly", "status", name}) | ||
if err != nil { | ||
level.Debug(t.logger).Log("msg", "Error execing for status", "name", name, "err", err) | ||
continue | ||
} | ||
|
||
status, err := parseStatus(output) | ||
if err != nil { | ||
level.Info(t.logger).Log("msg", "Error parsing status", "name", name, "err", err) | ||
continue | ||
} | ||
|
||
for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) { | ||
flatData, err := t.flattenOutput(dataQuery, status) | ||
if err != nil { | ||
level.Info(t.logger).Log("msg", "flatten failed", "err", err) | ||
continue | ||
} | ||
|
||
rowData := map[string]string{"name": name} | ||
|
||
results = append(results, dataflattentable.ToMap(flatData, dataQuery, rowData)...) | ||
} | ||
} | ||
|
||
return results, nil | ||
} | ||
|
||
func (t *Table) flattenOutput(dataQuery string, status map[string]interface{}) ([]dataflatten.Row, error) { | ||
flattenOpts := []dataflatten.FlattenOpts{ | ||
dataflatten.WithLogger(t.logger), | ||
dataflatten.WithQuery(strings.Split(dataQuery, "/")), | ||
} | ||
|
||
return dataflatten.Flatten(status, flattenOpts...) | ||
} |
11 changes: 11 additions & 0 deletions
11
pkg/osquery/tables/cryptsetup/testdata/status-active-luks1.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/dev/mapper/dm-crypto-luks1 is active and is in use. | ||
type: LUKS1 | ||
cipher: aes-xts-plain64 | ||
keysize: 512 bits | ||
key location: dm-crypt | ||
device: /dev/sdc1 | ||
sector size: 512 | ||
offset: 4096 sectors | ||
size: 8382464 sectors | ||
mode: read/write | ||
|
10 changes: 10 additions & 0 deletions
10
pkg/osquery/tables/cryptsetup/testdata/status-active-luks2.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/dev/mapper/dm-crypto-luks2 is active and is in use. | ||
type: LUKS2 | ||
cipher: aes-xts-plain64 | ||
keysize: 512 bits | ||
key location: keyring | ||
device: /dev/sdc2 | ||
sector size: 512 | ||
offset: 32768 sectors | ||
size: 8355840 sectors | ||
mode: read/write |
10 changes: 10 additions & 0 deletions
10
pkg/osquery/tables/cryptsetup/testdata/status-active-mounted.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/dev/mapper/dm-crypto-plain is active and is in use. | ||
type: PLAIN | ||
cipher: aes-cbc-essiv:sha256 | ||
keysize: 256 bits | ||
key location: dm-crypt | ||
device: /dev/sdc3 | ||
sector size: 512 | ||
offset: 0 sectors | ||
size: 8388608 sectors | ||
mode: read/write |
10 changes: 10 additions & 0 deletions
10
pkg/osquery/tables/cryptsetup/testdata/status-active-umounted.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/dev/mapper/dm-crypto-plain is active. | ||
type: PLAIN | ||
cipher: aes-cbc-essiv:sha256 | ||
keysize: 256 bits | ||
key location: dm-crypt | ||
device: /dev/sdc3 | ||
sector size: 512 | ||
offset: 0 sectors | ||
size: 8388608 sectors | ||
mode: read/write |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/dev/mapper/dm-crypto-plain is active and is in use. | ||
type: PLAIN | ||
cipher: aes-cbc-essiv:sha256 | ||
keysize: 256 bits | ||
key location: dm-crypt | ||
device: /dev/sdc3 | ||
sector size: 512 | ||
offset: 0 sectors | ||
size: 8388608 sectors | ||
mode: read/write |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Device sdc3 not found |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/dev/mapper/dm-crypto-plain is inactive. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/dev/mapper/dm-crypto-unknown is inactive. |