How to use Net::DNS::RR:TSIG with key files generated by ddns-confgen? - perl

I have been using nsupdate for a long time in various scripts dealing with dynamic DNS zone updates without any issue. I have always used TSIG to authenticate the requests against the DNS server, where the keys have been generated by ddns-confgen. That means that I didn't use key pairs like those generated by dnssec-keygen; rather, the keys file format is like the following:
key "ddns-key.my.domain.example" {
algorithm hmac-sha512;
secret "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefijklmnopqrstuvwxyzabcdefghij==";
};
The respective zone configuration then contains:
update-policy {
grant ddns-key.my.domain.example name host.my.domain.example ANY;
};
Now I have a more complicated task and try to solve it by a Perl script. I have studied the documentation of Perl::DNS::Update, Perl::DNS and Perl::DNS:RR:TSIG and have studied a lot of examples which should make the usage of those modules clear.
However, each example I saw, when coming to signing a request via TSIG, used key files in the format dnssec-keygen produces, and not key files in the format I have. And indeed, something like
$o_Resolver = new Net::DNS::Resolver(nameservers => ['127.0.0.1']);
$o_Update = new Net::DNS::Update('my.domain.example', 'IN');
$o_Update -> push(update => rr_del('host A'));
$o_Update -> push(update => rr_add('host 1800 IN A 192.0.2.1'));
$o_Update -> sign_tsig('/etc/bind/ddns-key.my.domain.example.key');
$o_Reply = ($o_Resolver -> send($o_Update));
does not work, producing the following message:
TSIG: unable to sign packet at /path/to/script.pl line 240.
unknown type "ddns-key.my.domain.example" at /usr/local/share/perl/5.20.2/Net/DNS/RR.pm line 669.
file /etc/bind/ddns-key.my.domain.example.key line 1
at /usr/local/share/perl/5.20.2/Net/DNS/RR/TSIG.pm line 403.
TSIG: unable to sign packet at /path/to/script.pl line 240.
I suppose I now have two options: Either use keys in the format dnssec-keygen produces, which seem to be directly usable with Net::DNS and its friends, or construct the TSIG key manually as shown in the docs:
my $key_name = 'tsig-key';
my $key = 'awwLOtRfpGE+rRKF2+DEiw==';
my $tsig = new Net::DNS::RR("$key_name TSIG $key");
$tsig->fudge(60);
my $update = new Net::DNS::Update('example.com');
$update->push( update => rr_add('foo.example.com A 10.1.2.3') );
$update->push( additional => $tsig );
[Of course, I wouldn't hard-code the key in my Perl script, but read it from the key file instead.]
Switching to another key file format would mean changing the DNS server configuration, which is not an elegant solution. "Manually" reading the key files and then "manually" constructing the keys is not very satisfying either, hence the question:
Did I understand correctly that it is not possible to use key files in the ddns-confgen format directly with Net::DNS and its sub-modules to TSIG-sign DNS update requests?

Related

Redis Hashes store with new line key value

I want to store data in Redis Hashes. Data is as below (Key = Value):
30.2.25=REF_IP
30.2.24=MY_HOST_IP
30.2.32=PEER_IP
30.2.32=IM_USER_MY_HOST
30.2.2=23992
Easy way to store this info in redis is below :
hmset info 30.2.25 REF_IP 30.2.24 MY_HOST_IP 30.2.32 PEER_IP 30.2.32 IM_USER_MY_HOST 30.2.2 23992
Considering I have 1000's key value and want to change few (actually so many) values in one go so searching and editing value in above command is too painful.
i want some way to execute command in below manner, that is nice formatted command with new line after every key value :
hmset info
30.2.25 REF_IP
30.2.24 MY_HOST_IP
30.2.32 PEER_IP
30.2.32 IM_USER_MY_HOST
30.2.2 23992
Is it possible to do so ?
Currently when i copy above formatted command and paste, it ignore test after new line and giving below error which is obvious because argument is wrong due to new line.
hmset info
(error) ERR wrong number of arguments for 'hmset' command
Can anyone help please. Thanks.
Assuming you are talking about using redis-cli, there is no way to support this at the moment. There is an open issue for this. See https://github.com/antirez/redis/issues/3474
As per Redis 4.0.0, HMSET is considered deprecated. You should use HSET instead. https://redis.io/commands/hset
You can use a transaction if you want to ensure all HSETs are done at the same time, and still enter them one line at a time.
MULTI
HSET info 30.2.25 REF_IP
HSET info 30.2.24 MY_HOST_IP
...
EXEC
The commands will be sent to the server one line at a time, but they are queued and only executed at the EXEC command.
You may use another client, say in Python, and then do something fancier as well to condense your field-value hsets into one command.

How to use a private key obtained from a database pem format phpseclib

I have stored some pem files in a database, I now wish to use this to load the key in order to ssh into the box in question. however when my code reaches the $key->load( $pub ); line in my code it errors. the code was previously working by having the keys as strings in the files but prefer to have them in the database as it'll be easier to maintain as more keys are required.
I'm using i think phpseclib as there is no loadKeys function in the RSA file. Code works when the key is pasted into the script. I pasted the code into the database directly using phpmyadmin. My dev machine is Win 10 but when live It will be on an internal linux server
$lightsail = new lightsail();
$pub = $lightsail->getPemByName();
$pub = str_replace("\r", '', $pub ); // Noticed key returned had \r\n so corrected it but still fails
$key = new RSA();
$key->load( $pub );
Error i see is as follows
( ! ) Fatal error: Uncaught Error: Call to a member function toBytes() on string in something\phpseclib\Crypt\RSA.php on line 724
( ! ) Error: Call to a member function toBytes() on string in something\phpseclib\Crypt\RSA.php on line 724
Call Stack
# Time Memory Function Location
1 0.2199 430880 {main}( ) ...\dequeue.php:0
2 40.8792 1169040 backup->backupDatabase( ) ...\dequeue.php:181
3 76.7275 2748016 phpseclib\Crypt\RSA->load( ???, ??? ) ...\my.class.php:986
I'm thinking that it is the paste of the pem into phpmyadmin is the issue here? I've been unable to find any examples which use $key->load() instead of $key->loadKey() with a pem file and even less of using a pem key in a database.
My next approach will be to load the file contents if this approach is after all a dead end.
In the end i loaded it from the file ( $pub = file_get_contents($path);), this worked.

Foreman/Puppet module pdxcat/collectd. Syntax of hash

New to puppet and trying to get this module to work, but finding it quite frustrating.
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: can't convert String into Hash at /etc/puppet/modules/collectd/manifests/plugin/network.pp:28
The plugin I am struggling with is this one:
https://forge.puppetlabs.com/pdxcat/collectd#class-collectdpluginnetwork
The value I am trying to set is servers under collectd::plugin::network
I have tried:
('127.0.0.1': port => 25826,) and
('hostname' '127.0.0.1' 'port' 25826) and '127.0.0.1': port => 25826,
and a myriad of other options.
Could somebody please let me know how to write a valid hash?
The manifest:
[root#foreman ~]# cat /etc/puppet/modules/collectd/manifests/plugin/network/server.pp
#
define collectd::plugin::network::server (
$ensure = 'present',
$username = undef,
$password = undef,
$port = undef,
$securitylevel = undef,
$interface = undef,
) {
include collectd::params
include collectd::plugin::network
$conf_dir = $collectd::params::plugin_conf_dir
validate_string($name)
file { "${conf_dir}/network-server-${name}.conf":
ensure => $ensure,
mode => '0640',
owner => 'root',
group => $collectd::params::root_group,
content => template('collectd/plugin/network/server.conf.erb'),
notify => Service['collectd'],
}
}
Could you please attach fragment of manifest that is not working ?
Here you can find the description of puppet types: https://docs.puppetlabs.com/puppet/latest/reference/lang_datatypes.html, also about hashes.
According to documentation:
Hashes are written as key/value pairs surrounded by curly braces; a key is separated from its value by a => (arrow, fat comma, or hash rocket), and adjacent pairs are separated by commas. An optional trailing comma is allowed between the final value and the closing curly brace.
{ key1 => 'val1', key2 => 'val2' }
So definitely you have to change "(" brackets to "{". Also after key should be "=>" not ":"
Something like this should be working:
servers => { '127.0.0.1' =>
{ 'port' => '25826', },
}
When putting hash data into a Foreman smart class parameter (or smart variable), you need to do two things to get it passed to Puppet correctly:
Set the data type on the parameter to Hash, JSON or YAML
Use JSON or YAML to represent the data
The first will ensure that Puppet is given an actual hash of data, instead of a string that looks like a hash (which I think was probably the cause of the error you got), and the second allows Foreman to parse what you enter.
Navigate in Foreman to Configure > Puppet classes > collectd::plugin::network > Smart class parameters > servers, and set Type to JSON (or Hash, or YAML, if you prefer).
Next, change the value of the parameter (either the default or an override further down) to:
{"127.0.0.1":{"port":"25826"}}
This is JSON syntax, which if you compare it to Puppet's DSL, you'll note it uses colons instead of => for key/value separators, and uses double quotes only for strings. Beware that trailing commas aren't valid in JSON.
Pretty much you can copy the example parameter from the documentation or from the other answer, substituting the separators and quotes to convert it to JSON.
Equivalent YAML format would be:
---
127.0.0.1:
port: "25826"
Foreman will accept either when in "Hash" or "Array" mode, and IIRC it will store/retrieve it in YAML format by default.
If Foreman gives an error when saving the parameter, it might be due to the format of the data you're entering. There are numerous validation and linting tools for both JSON and YAML, e.g. jsonlint.com or json_verify (part of the yajl package), so run the data through it first.
There's more information about complex data types in the Foreman manual in section 4.2.6 Smart Matchers.
If you still get an error from Puppet after this, please go to the host page in Foreman, click the YAML button and copy/paste the classes: section of the YAML output (which is what's passed to Puppet), being careful to preserve whitespace.

How do you use SHA256 to create a token of key,value pairs and a secret signature?

I want to validate some hidden input fields (to make sure they arent changed on submission) with the help of a sha-encoded string of the key value pairs of these hidden fields. I saw examples of this online but I didnt understand how to encode and
decode the values with a dynamic secret value. Can someone help me understand how to do this in perl?
Also which signature type (MD5, SHA1, SHA256, etc), has a good balance of performance and security?
update
So, how do you decode the string once you get it encoded?
What you really need is not a plain hash function, but a message authentication code such as HMAC. Since you say you'd like to use SHA-256, you might like HMAC_SHA256, which is available in Perl via the Digest::SHA module:
use Digest::SHA qw(hmac_sha256_base64);
my $mac = hmac_sha256_base64( $string, $key );
Here, $key is an arbitrary key, which you should keep secret, and $string contains the data you want to sign. To apply this to a more complex data structure (such as a hash of key–value pairs), you first need to convert it to a string. There are several ways to do that; for example, you could use Storable:
use Storable qw(freeze);
sub pairs_to_string {
local $Storable::canonical = 1;
my %hash = #_;
return freeze( \%hash );
}
You could also URL-encoding, as suggested by David Schwartz. The important thing is that, whatever method you use, it should always return the exact same string when given the same hash as input.
Then, before sending the data to the user, you calculate a MAC for them and include it as an extra field in the data. When you receive the data back, you remove the MAC field (and save its value), recalculate the MAC for the remaining fields and compare it to the value you received. If they don't match, someone (or something) has tampered with the data. Like this:
my $key = "secret";
sub mac { hmac_sha256_base64( pairs_to_string(#_), $key ) }
# before sending data to client:
my %data = (foo => "something", bar => "whatever");
$data{mac} = mac( %data );
# after receiving %data back from client:
my $mac = delete $data{mac};
die "MAC mismatch" if $mac ne mac( %data );
Note that there are some potential tricks this technique doesn't automatically prevent, such as replay attacks: once you send the data and MAC to the user, they'll learn the MAC corresponding to the particular set of data, and could potentially replace the fields in a later form with values saved from an earlier form. To protect yourself against such attacks, you should include enough identifying information in the data protected by the MAC to ensure that you can detect any potentially harmful replays. Ideally, you'd want to include a unique ID in every form and check that no ID is ever submitted twice, but that may not always be practical. Failing that, it may be a good idea to include a user ID (so that a malicious user can't trick someone else into submitting their data) and a form ID (so that a user can't copy data from one form to another) and perhaps a timestamp and/or a session ID (so that you can reject old data) in the form (and in the MAC calculation).
I don't know what you mean by "unpack", but you can't get original string from the hash.
Let's understand the problem: you render some hidden fields and you want to make sure that they're submitted unchanged, right? Here's how you can ensure that.
Let's suppose you have two variables:
first: foo
second: bar
You can hash them together with a secret key:
secret_key = "ysEJbKTuJU6u"
source_string = secret_key + "first" + "foo" + "second" + "bar"
hash = MD5(source_string)
# => "1adfda97d28af6535ef7e8fcb921d3f0"
Now you can render your markup:
<input type="hidden" name="first" value="foo" />
<input type="hidden" name="second" value="bar" />
<input type="hidden" name="hash" value="1adfda97d28af6535ef7e8fcb921d3f0">
Upon form submission, you get values of first and second fields, concat them to your secret key in a similar manner and hash again.
If hashes are equal, your values haven't been changed.
Note: never render secret key to the client. And sort key/value pairs before hashing (to eliminate dependency on order).
( disclaimer: I am not a crypto person, so you may just stop reading now)
As for performance/security, even though MD5 was found to have a weakness, it's still pretty usable, IMHO. SHA1 has a theoretical weakness, although no successful attack has been made yet. There are no known weaknesses in SHA-256.
For this application, any of the encryption algorithms is fine. You can pack the values any way you want, so long as it's repeatable. One common method is to pack the fields into a string the same way you would encode them into a URL for a GET request (name=value).
To compute the hash, create a text secret that can be whatever you want. It should be at least 12 bytes long though. Compute the hash of the secret concatenated with the packed fields and append that onto the end.
So, say you picked MD5, a secret of JS90320ERHe2 and you have these fields:
first_name = Jack
last_name = Smith
other_field = 7=2
First, URL encode it:
first_name=Jack&last_name=Smith&other_field=7%3d=2
Then compute the MD5 hash of
JS90320ERHe2first_name=Jack&last_name=Smith&other_field=7%3d=2
Which is 6d0fa69703935efaa183be57f81d38ea. The final encoded field is:
first_name=Jack&last_name=Smith&other_field=7%3d=2&hash=6d0fa69703935efaa183be57f81d38ea
So that's what you pass to the user. To validate it, remove the hash from the end, compute the MD5 hash by concatenating what's left with the secret, and if the hashes match, the field hasn't been tampered with.
Nobody can compute their own valid MD5 because they don't know to prefix the string with.
Note that an adversary can re-use any old valid value set. They just can't create their own value set from scratch or modify an existing one and have it test valid. So make sure you include something in the information so you can verify that it is suitable for the purpose it has been used.

Special character handling when fetching data from MS SQL Server using Perl DBD

I have an MS SQL Server 2008 Database, from which I am fetching data using perl DBD::Sybase module. But there are some special characters in the DB, like the Copyright symbol, Trademark symbol etc., which are not getting imported properly. Perl seems to change all of these special characters to a Question mark character. Is there a way to fix this?
I have tried specifying charset=utf8 in the connection string. The doc mentions a syb_enable_utf8 (bool) setting, but whenever I try that, I get an error:
Can't locate object method "syb_enable_utf8" via package "DBI::db"
One solution I found was this:
use Encode qw(encode_utf8);
Then, wherever you are writing data to a file or anywhere else, use Encode::encode_utf8($data);
where $data is the column/value which you have fetched from MSSQL.
I don't use DBD::Sybase but a) I use a lot of other DBDs and b) I am currently collecting information about unicode support in DBDs. According to the pod you need at least OpenClient 15.x when using syb_enable_utf8. Are you using 15.x or later? Perhaps syb_enable_utf8 is not defined if your client is less than 15.x or perhaps you have too old a version of DBD::Sybase. Unfortunately I cannot see from the Changes file when syb_enable_utf8 was added.
However, when you say "can't locate method" I think that is a clue as syb_enable_utf8 is not a method, it is an attribute (it is under Sybase Specific Attributes) in the pod. So you need to add it to your connect call or set it via a connection handle like this:
my $h = DBI->connect("dbi:Sybase:something","user","password", {syb_enable_utf8 => 1});
or
$h->{syb_enable_utf8} = 1;
You should also read the bits in the pod describing what happens when syb_enable_utf8 is set as it appears from the documents it only applies to UNIVARCHAR, UNICHAR, and UNITEXT columns.
Lastly, you need to ensure you insert the data correctly in the first place. I'd guess if it is not inserted from Perl with syb_enable_utf8 and charset=utf8 and your data is not proper unicode characters in Perl before you insert you'll get garbage back.
The comment Raze2dust made had nothing to do with your issue but is worth heeding if you are going to write the data retrieved from your database elsewhere. Just remember to decode any data input to your script and encode any data output.