File-SOPS

 view release on metacpan or  search on metacpan

lib/File/SOPS/Metadata.pm  view on Meta::CPAN

        = _decode_int_field('shamir_threshold', $extra{shamir_threshold})
        if exists $extra{shamir_threshold};

    return $class->new(
        extra              => \%extra,
        age                => $hash->{age}                // [],
        pgp                => $hash->{pgp}                // [],
        kms                => $hash->{kms}                // [],
        gcp_kms            => $hash->{gcp_kms}            // [],
        azure_kv           => $hash->{azure_kv}           // [],
        hc_vault           => $hash->{hc_vault}           // [],
        mac                => $hash->{mac},
        lastmodified       => _decode_lastmodified($hash->{lastmodified}),
        version            => $hash->{version}            // $SOPS_VERSION,
        unencrypted_suffix => $hash->{unencrypted_suffix},
        encrypted_suffix   => $hash->{encrypted_suffix},
        unencrypted_regex  => $hash->{unencrypted_regex},
        encrypted_regex    => $hash->{encrypted_regex},
        mac_only_encrypted =>
            _decode_bool_field('mac_only_encrypted', $hash->{mac_only_encrypted}),
    );
}


sub to_hash {
    my ($self) = @_;

    # Unmodelled fields go down first so that a modelled one always wins: the
    # attributes are the truth about this document, extra is only what nobody
    # here has an opinion about.
    my $hash = {
        %{ $self->extra },
        kms      => $self->kms,
        gcp_kms  => $self->gcp_kms,
        azure_kv => $self->azure_kv,
        hc_vault => $self->hc_vault,
        age      => $self->age,
        pgp      => $self->pgp,
    };

    $hash->{lastmodified} = $self->lastmodified if defined $self->lastmodified;
    $hash->{mac}          = $self->mac          if defined $self->mac;
    $hash->{version}      = $self->version      if defined $self->version;

    $hash->{unencrypted_suffix} = $self->unencrypted_suffix
        if defined $self->unencrypted_suffix;
    $hash->{encrypted_suffix} = $self->encrypted_suffix
        if defined $self->encrypted_suffix;
    $hash->{unencrypted_regex} = $self->unencrypted_regex
        if defined $self->unencrypted_regex;
    $hash->{encrypted_regex} = $self->encrypted_regex
        if defined $self->encrypted_regex;
    # sops omits the key entirely when the option is off, so a false must not
    # be written back as `mac_only_encrypted: false`.
    $hash->{mac_only_encrypted} = JSON->true if $self->mac_only_encrypted;

    return $hash;
}


sub update_lastmodified {
    my ($self) = @_;
    $self->lastmodified(strftime('%Y-%m-%dT%H:%M:%SZ', gmtime));
    return $self;
}


sub add_age_recipient {
    my ($self, %args) = @_;
    my $recipient = $args{recipient} // croak "recipient required";
    my $enc       = $args{enc}       // croak "enc required";

    push @{$self->age}, {
        recipient => $recipient,
        enc       => $enc,
    };

    return $self;
}


sub get_age_encrypted_keys {
    my ($self) = @_;
    return @{$self->age};
}


###############################################################################
# The two regex rules, and the dialect they are matched in
###############################################################################

# unencrypted_regex and encrypted_regex are matched HERE with Perl and in sops
# with Go's RE2, and those are not the same dialect. Two consequences, both
# measured against sops 3.13.3 -- docs/adr/0048, k161.
#
# 1. RE2's \w \W \d \D \s \S \b \B and its POSIX classes are ASCII-only for
#    every subject. Perl's are Unicode-aware for any string carrying the UTF-8
#    flag -- which is every non-ASCII key our parsers produce. So an
#    `unencrypted_regex: ^\w+$` is a rule under which sops ENCRYPTS `café`,
#    `密`, `n٣` and `a<NBSP>b` and this library left them BARE. That is why the
#    patterns are compiled /a below: it is RE2's answer for these classes, and
#    it makes the answer independent of a flag ADR 0003 forbids reading
#    anywhere else in this distribution.
#
# 2. A pattern RE2 cannot COMPILE is not reported by sops. It matches with the
#    error discarded, so the rule silently matches NOTHING: measured,
#    `--encrypted-regex '(?=f)foo'` writes every value of the document in
#    PLAINTEXT at exit 0, and `--unencrypted-regex '(?=f)foo'` encrypts every
#    value. Neither is a classification that can be reproduced quietly, so the
#    constructs RE2 rejects are refused here instead.

# The escapes RE2 accepts, meaning by them what Perl means once the pattern is
# compiled /a. Every OTHER alphanumeric escape is refused, because RE2 either
# rejects it -- \Z \K \G \R \h \H \V \N \X \C \c \e \o \g \k \u \l \U \L are
# all "invalid escape sequence", measured through a .sops.yaml path_regex,
# which is the one place sops reports a compile error -- or reads it as
# something else:
#
#   \v   the vertical TAB to Go, the vertical-whitespace CLASS to Perl.
#   \Q   a quoted literal run to RE2, and NOTHING to Perl: \Q and \E are
#        double-quotish escapes, processed when a pattern is written out in



( run in 0.923 second using v1.01-cache-2.11-cpan-8fcc4e0cb81 )