-
Notifications
You must be signed in to change notification settings - Fork 341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add support for data transfer encryption via rc4 and aes #236
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ import ( | |
"strings" | ||
|
||
"github.com/colinmarc/hdfs/v2/hadoopconf" | ||
"github.com/colinmarc/hdfs/v2/internal/protocol/hadoop_common" | ||
hdfs "github.com/colinmarc/hdfs/v2/internal/protocol/hadoop_hdfs" | ||
"github.com/colinmarc/hdfs/v2/internal/rpc" | ||
krb "gopkg.in/jcmturner/gokrb5.v7/client" | ||
|
@@ -68,6 +69,13 @@ type ClientOptions struct { | |
// multi-namenode setup (for example: 'nn/_HOST'). It is required if | ||
// KerberosClient is provided. | ||
KerberosServicePrincipleName string | ||
// EncryptDataTransfer specifies whether or not data transfer for datanodes | ||
// needs to utilize encryption and thus do a negotiation to get data | ||
// this is specified by dfs.encrypt.data.transfer in the config | ||
EncryptDataTransfer bool | ||
// SecureDataNode specifies whether we're protecting our data transfer | ||
// communication via dfs.data.transfer.protection | ||
SecureDataNode bool | ||
} | ||
|
||
// ClientOptionsFromConf attempts to load any relevant configuration options | ||
|
@@ -116,6 +124,26 @@ func ClientOptionsFromConf(conf hadoopconf.HadoopConf) ClientOptions { | |
options.KerberosServicePrincipleName = strings.Split(conf["dfs.namenode.kerberos.principal"], "@")[0] | ||
} | ||
|
||
dataTransferProt := strings.Split(strings.ToLower(conf["dfs.data.transfer.protection"]), ",") | ||
for _, val := range dataTransferProt { | ||
switch val { | ||
case "privacy": | ||
options.EncryptDataTransfer = true | ||
fallthrough | ||
case "integrity", "authentication": | ||
options.SecureDataNode = true | ||
} | ||
} | ||
|
||
// dfs.encrypt.data.transfer set to true overrides dfs.data.transfer.protection and | ||
// requires both privacy and integrity for communication. If dfs.encrypt.data.transfer is | ||
// "true" we explicitly set EncryptDataTransfer and SecureDataNode to true, regardless of | ||
// what they already were. | ||
if conf["dfs.encrypt.data.transfer"] == "true" { | ||
options.EncryptDataTransfer = true | ||
options.SecureDataNode = true | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment for this method needs to be updated (to say that we munge these fields) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment updated. let me know if this is good. |
||
return options | ||
} | ||
|
||
|
@@ -148,6 +176,20 @@ func NewClient(options ClientOptions) (*Client, error) { | |
return &Client{namenode: namenode, options: options}, nil | ||
} | ||
|
||
func (c *Client) datanodeDialFunc(token *hadoop_common.TokenProto) func(ctx context.Context, network, addr string) (net.Conn, error) { | ||
if c.options.EncryptDataTransfer || c.options.SecureDataNode { | ||
return (&rpc.DatanodeSaslDialer{ | ||
Dialer: c.options.DatanodeDialFunc, | ||
Key: c.namenode.GetEncryptionKeys(), | ||
Privacy: c.options.EncryptDataTransfer, | ||
Integrity: c.options.SecureDataNode, | ||
Token: token, | ||
}).DialContext | ||
} | ||
|
||
return c.options.DatanodeDialFunc | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we avoid wrapping it at all if those options are false? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you look at the definition for |
||
|
||
// New returns Client connected to the namenode(s) specified by address, or an | ||
// error if it can't connect. Multiple namenodes can be specified by separating | ||
// them with commas, for example "nn1:9000,nn2:9000". | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package rpc | ||
|
||
import ( | ||
"crypto/aes" | ||
"crypto/cipher" | ||
"net" | ||
"time" | ||
) | ||
|
||
type aesConn struct { | ||
conn net.Conn | ||
|
||
encStream cipher.StreamWriter | ||
decStream cipher.StreamReader | ||
} | ||
|
||
func newAesConn(conn net.Conn, inKey, outKey, inIv, outIv []byte) (net.Conn, error) { | ||
c := &aesConn{conn: conn} | ||
|
||
encBlock, err := aes.NewCipher(inKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
decBlock, err := aes.NewCipher(outKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
c.encStream = cipher.StreamWriter{S: cipher.NewCTR(encBlock, inIv), W: conn} | ||
c.decStream = cipher.StreamReader{S: cipher.NewCTR(decBlock, outIv), R: conn} | ||
return c, nil | ||
} | ||
|
||
func (d *aesConn) Close() error { | ||
return d.conn.Close() | ||
} | ||
|
||
func (d *aesConn) LocalAddr() net.Addr { | ||
return d.conn.LocalAddr() | ||
} | ||
|
||
func (d *aesConn) RemoteAddr() net.Addr { | ||
return d.conn.RemoteAddr() | ||
} | ||
|
||
func (d *aesConn) SetDeadline(t time.Time) error { | ||
return d.conn.SetDeadline(t) | ||
} | ||
|
||
func (d *aesConn) SetReadDeadline(t time.Time) error { | ||
return d.conn.SetReadDeadline(t) | ||
} | ||
|
||
func (d *aesConn) SetWriteDeadline(t time.Time) error { | ||
return d.conn.SetWriteDeadline(t) | ||
} | ||
|
||
func (d *aesConn) Write(b []byte) (n int, err error) { | ||
return d.encStream.Write(b) | ||
} | ||
|
||
func (d *aesConn) Read(b []byte) (n int, err error) { | ||
return d.decStream.Read(b) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -330,33 +330,26 @@ func (s *blockWriteStream) writePacket(p outboundPacket) error { | |
DataLen: proto.Int32(int32(len(p.data))), | ||
} | ||
|
||
header := make([]byte, 6) | ||
// Don't ask me why this doesn't include the header proto... | ||
totalLength := len(p.data) + len(p.checksums) + 4 | ||
|
||
header := make([]byte, 6, 6+totalLength) | ||
infoBytes, err := proto.Marshal(headerInfo) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Don't ask me why this doesn't include the header proto... | ||
totalLength := len(p.data) + len(p.checksums) + 4 | ||
binary.BigEndian.PutUint32(header, uint32(totalLength)) | ||
binary.BigEndian.PutUint16(header[4:], uint16(len(infoBytes))) | ||
header = append(header, infoBytes...) | ||
header = append(header, p.checksums...) | ||
header = append(header, p.data...) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you give There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done! |
||
|
||
_, err = s.conn.Write(header) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = s.conn.Write(p.checksums) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = s.conn.Write(p.data) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this was something else, actually? We've always sent the block token, and I think this is just a server-side option for verifying them (with or without kerberos). In any case it should be
EnableBlockAccessToken
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out you're right, this should be changed to reference
dfs.data.transfer.protection
notdfs.block.access.token.enable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know if the new comments / updated name/comment is good.