Skip to content

Commit

Permalink
Working, but without tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pgporada committed Aug 15, 2024
1 parent 063b5a1 commit bb4a68a
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 18 deletions.
140 changes: 123 additions & 17 deletions cmd/admin/pause.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ package main

import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/csv"
"errors"
"flag"
"fmt"
"io"
"os"
"slices"
"strconv"
"strings"

"github.com/letsencrypt/boulder/identifier"
rapb "github.com/letsencrypt/boulder/ra/proto"
sapb "github.com/letsencrypt/boulder/sa/proto"
)

// subcommandPauseBatch encapsulates the "admin pause-batch" commands.
Expand All @@ -33,13 +39,17 @@ func (p *subcommandPauseBatch) Run(ctx context.Context, a *admin) error {
return errors.New("the -file flag is required")
}

_, err := a.readPausedAccountFile(p.file)
identifiers, err := a.readPausedAccountFile(p.file)
if err != nil {
return err
}

// TODO: Fix
return errors.New("no action to perform on the given CSV file was specified")
err = a.pauseIdentifiers(identifiers)
if err != nil {
return err
}

return nil
}

// subcommandUnpauseBatch encapsulates the "admin unpause-batch" commands.
Expand All @@ -62,13 +72,17 @@ func (u *subcommandUnpauseBatch) Run(ctx context.Context, a *admin) error {
return errors.New("the -file flag is required")
}

_, err := a.readPausedAccountFile(u.file)
identifiers, err := a.readPausedAccountFile(u.file)
if err != nil {
return err
}

err = a.unpauseAccount(identifiers)
if err != nil {
return err
}

// TODO: Fix
return errors.New("no action to perform on the given CSV file was specified")
return nil
}

// pauseData contains
Expand All @@ -78,6 +92,51 @@ type pauseData struct {
identifierValue []string
}

// pauseIdentifiers allows administratively pausing a set of domain names for an
// account.
func (a *admin) pauseIdentifiers(pd []pauseData) error {
if len(pd) <= 0 {
return errors.New("cannot pause identifiers because no pauseData was sent")
}

for _, data := range pd {
req := sapb.PauseRequest{
RegistrationID: data.accountID,
Identifiers: []*sapb.Identifier{{
Type: string(data.identifierType),
Value: strings.Join(data.identifierValue, ","),
},
},
}
_, err := a.sac.PauseIdentifiers(context.Background(), &req)
if err != nil {
return err
}
}

return nil
}

// unpauseAccount allows administratively unpausing all identifiers for an
// account.
func (a *admin) unpauseAccount(pd []pauseData) error {
if len(pd) <= 0 {
return errors.New("cannot unpause accounts because no pauseData was sent")
}

for _, data := range pd {
req := rapb.UnpauseAccountRequest{
RegistrationID: data.accountID,
}
_, err := a.rac.UnpauseAccount(context.Background(), &req)
if err != nil {
return err
}
}

return nil
}

// readPausedAccountFile parses the contents of a CSV into a slice of
// `pauseData` objects. It will return an error if an individual record is
// malformed.
Expand All @@ -94,32 +153,79 @@ func (a *admin) readPausedAccountFile(filePath string) ([]pauseData, error) {
reader.FieldsPerRecord = -1
reader.TrimLeadingSpace = true

var data []pauseData
var parsedRecords []pauseData
hashToPauseData := make(map[string]pauseData)
lineCounter := 1

defer func() {
var record string
if len(hashToPauseData) == 1 {
record = "record"
} else {
record = "records"
}
fmt.Fprintf(os.Stderr, "detected %d valid %s from input file\n", len(hashToPauseData), record)
}()

// Parse file contents
// Process contents of the CSV file
for {
record, err := reader.Read()
if errors.Is(err, io.EOF) {
// Finished parsing the file.
if len(record) == 0 {
return nil, errors.New("no records found")
//if len(record) == 0 {
// return nil, errors.New("no records found")
//}

for _, value := range hashToPauseData {
parsedRecords = append(parsedRecords, value)
}
// TODO: return valid data or something
return data, nil

return parsedRecords, nil
} else if err != nil {
return nil, err
}

// Ensure the first column of each record can be parsed as a valid
// accountID.
recordID := record[0]
accountID, err := strconv.ParseInt(recordID, 10, 64)
if err != nil {
return nil, fmt.Errorf("%q couldn't be parsed as an accountID due to: %s", recordID, err)
fmt.Fprintf(os.Stderr, "skipping: malformed accountID entry on line %d\n", lineCounter)
continue
}

// Ensure that an identifier type is present, otherwise skip the line.
if len(record[1]) == 0 {
fmt.Fprintf(os.Stderr, "skipping: malformed identifierType entry on line %d\n", lineCounter)
continue
}
identifierType := identifier.IdentifierType(record[1])
identifierValue := record[2:]

fmt.Printf("Loaded: %d,%s,%s", accountID, identifierType, identifierValue[:])
if len(record) < 3 {
fmt.Fprintf(os.Stderr, "skipping: malformed identifierValue entry on line %d\n", lineCounter)
continue
}
// The remaining fields are the domain names.
identifierValue := record[2:]
slices.Sort(identifierValue)

// Construct a hash over the parsed line from the CSV. The hash will be
// used as a key mapping to a pauseData object containing the fields we
// wish to operate on.
hash := sha256.New()
var recordBytes []byte
recordBytes = append(recordBytes, byte(accountID))

Check failure

Code scanning / CodeQL

Incorrect conversion between integer types High

Incorrect conversion of a signed 64-bit integer from
strconv.ParseInt
to a lower bit size type uint8 without an upper bound check.
recordBytes = append(recordBytes, []byte(identifierType)...)
recordBytes = append(recordBytes, []byte(strings.Join(identifierValue, ""))...)
b64Hash := base64.StdEncoding.EncodeToString(hash.Sum(recordBytes))

if _, ok := hashToPauseData[b64Hash]; !ok {
hashToPauseData[b64Hash] = pauseData{
accountID: accountID,
identifierType: identifierType,
identifierValue: identifierValue,
}
} else {
fmt.Fprintf(os.Stderr, "skipping: duplicate entry on line %d: %d,%s,%s\n", lineCounter, accountID, identifierType, identifierValue[:])
}
lineCounter++
}
}
Empty file.
1 change: 1 addition & 0 deletions cmd/admin/testdata/test_pause_data_invalid_noDomain.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1,dns
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
6 changes: 6 additions & 0 deletions cmd/admin/testdata/test_pause_data_valid.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
1,dns,example.com,example.net
2,dns,example.org
1,dns,example.com,example.net
1,dns,example.com,example.net
3,dns,example.gov
3,dns,example.gov
2 changes: 1 addition & 1 deletion test/certs/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ ipki() (
minica -domains redis -ip-addresses 10.33.33.2,10.33.33.3,10.33.33.4,10.33.33.5,10.33.33.6,10.33.33.7,10.33.33.8,10.33.33.9

# Used by Boulder gRPC services as both server and client mTLS certificates.
for SERVICE in admin-revoker expiration-mailer ocsp-responder consul \
for SERVICE in admin admin-revoker expiration-mailer ocsp-responder consul \
wfe akamai-purger bad-key-revoker crl-updater crl-storer \
health-checker rocsp-tool sfe; do
minica -domains "${SERVICE}.boulder" &
Expand Down
1 change: 1 addition & 0 deletions test/config-next/ra.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"ra.RegistrationAuthority": {
"clientNames": [
"admin-revoker.boulder",
"admin.boulder",
"bad-key-revoker.boulder",
"ocsp-responder.boulder",
"wfe.boulder",
Expand Down
1 change: 1 addition & 0 deletions test/config/ra.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"ra.RegistrationAuthority": {
"clientNames": [
"admin-revoker.boulder",
"admin.boulder",
"bad-key-revoker.boulder",
"ocsp-responder.boulder",
"sfe.boulder",
Expand Down

0 comments on commit bb4a68a

Please sign in to comment.