Skip to content
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

haproxy PROXY protocol support #49

Closed
iuri-gg opened this issue Jul 22, 2023 · 45 comments
Closed

haproxy PROXY protocol support #49

iuri-gg opened this issue Jul 22, 2023 · 45 comments
Assignees
Milestone

Comments

@iuri-gg
Copy link
Contributor

iuri-gg commented Jul 22, 2023

Is your feature request related to a problem? Please describe.
I want to add use haproxy for load-balancing and high availability (multiple servers)

Describe the solution you'd like
I want server to handle PROXY command and update local and remote IPs, Ports, and hostnames accordingly.

Describe alternatives you've considered
I was able to make it work by overriding on_process_line_unknown_event. What is left is to parse IPv4 or IPv6 addresses and ports. However, I am not sure it is a proper solution for handling and setting remote and local ips, ports and hostnames. I was wondering if it should be handled within the library itself

  def on_process_line_unknown_event(_ctx, line)
    case line
    when (/^PROXY .*$/i)
      ctx[:server]
      "PROXY OK"
    else
      raise Smtpd500Exception
    end
  end
@TomFreudenberg
Copy link
Member

Hi Iuri @iuri-gg

great to hear from you :-)

The on_process_line_unknown_event is exactly build for such a kind of not yet supported commands and processings. Also acessing and changing the ctx content is allowed in that event.

In general I am interested to include that PROXY command into the library directly. Do you have a RFC or additional information to that command?

Cheers, Tom

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Jul 22, 2023

Hi Tom. Good to hear from you again too!

I have the following information:

I was initially thinking to see how postfix (or another open-source SMTP server) implements it. The binary format (v2) is harder but the text format (v1) ended up being easy. I have not done the IP and port parsing yet though.

Hope this helps.

@TomFreudenberg
Copy link
Member

Hi Iuri,

I checked the Proxy Protocol Doc but did not found enough SMTP related information yet. Just for my understanding of a configuration:

SMTP Sender -> PROXY (like HAProxy) -> MidiSmtpServer Instance
0.0.0.0     -> 1.2.3.4 (public IP)  -> 192.168.1.1 (internal IP)

Is that the setup?

When yes, then on a connection to the PROXY, it will use the Proxy Protocol and forward the traffic to the MidiSmtpServer instance.

So then, we have to analyze the PROXY ... command and take the relavnt information into the SMTP document?

Are my assumptions correct?

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Jul 23, 2023

Yes Tom, your assumptions are correct.
for testing you can use following haproxy.cfg and Dockerfile to build an image:

haproxy.cfg

global
  log stdout local0 info

frontend ft_smtp
  bind :3535
  mode tcp
  timeout client 1m
  log global
  option tcplog
  default_backend bk_smtp

backend bk_smtp
  mode tcp
  log global
  timeout server 1m
  timeout connect 5s
  server my-smtp host.docker.internal:2525 send-proxy

Dockerfile

FROM haproxy:2.3
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg

then build the image and start the haproxy container:

docker build -t my-proxy
docker run --rm -it -p 3535:3535 my-haproxy

this will forward SMTP traffic from port 3535 to port 2525. If you are on macOS your local IP in the proxy will not be localhost due to the internal networking of docker.

I can also get you the SMTP log with and without a proxy if that will be helpful.

@TomFreudenberg
Copy link
Member

Hey Iuri

I can also get you the SMTP log with and without a proxy if that will be helpful.

yes, please - so I can have a first view.

Thanks for the provided information.

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Jul 23, 2023

With haproxy. Note: 172.17.0.1 is the client IP and 172.17.0.2 is haproxy IP.

Client connect from 127.0.0.1:58599 to 127.0.0.1:2525
>>> 220 smtp-test says welcome!

<<< PROXY TCP4 172.17.0.1 172.17.0.2 53396 3535

>>> PROXY OK
<<< EHLO ms-invite.eml

>>> 250-smtp-test at your service!
250-PIPELINING
250-AUTH LOGIN PLAIN
250-STARTTLS
250 OK
<<< STARTTLS

>>> 220 Ready to start TLS
<<< EHLO ms-invite.eml

>>> 250-smtp-test at your service!
250-PIPELINING
250-AUTH LOGIN PLAIN
250 OK
<<< AUTH PLAIN

>>> 334 
<<< AG1nb2tRZVlRUWkAZjlhVWs1YlhKQzN6UQ==

>>> 235 OK
<<< MAIL FROM:<elvis2@example.com>

>>> 250 OK
<<< RCPT TO:<chuck2@example.com>

>>> 250 OK
<<< DATA

>>> 354 Enter message, ending with "." on a line by itself
>>> 250 Requested mail action okay, completed

without haproxy:

Client connect from 127.0.0.1:58493 to 127.0.0.1:2525
>>> 220 smtp-test says welcome!

<<< EHLO ms-invite.eml

>>> 250-smtp-test at your service!
250-PIPELINING
250-AUTH LOGIN PLAIN
250-STARTTLS
250 OK
<<< STARTTLS

>>> 220 Ready to start TLS
<<< EHLO ms-invite.eml

>>> 250-smtp-test at your service!
250-PIPELINING
250-AUTH LOGIN PLAIN
250 OK
<<< AUTH PLAIN

>>> 334 
<<< AG1nb2tRZVlRUWkAZjlhVWs1YlhKQzN6UQ==

>>> 235 OK
<<< MAIL FROM:<elvis2@example.com>

>>> 250 OK
<<< RCPT TO:<chuck2@example.com>

>>> 250 OK
<<< DATA

>>> 354 Enter message, ending with "." on a line by itself
>>> 250 Requested mail action okay, completed
<<< QUIT

and below is my naive implementation that works:

  def on_process_line_unknown_event(ctx, line)
    case line
    when (/^PROXY .*$/i)
      _cmd, _proto, src_ip, _dst_ip, src_port, _dst_port = line.split(" ")
      ctx[:server][:remote_ip] = src_ip
      ctx[:server][:remote_port] = src_port
      ctx[:server][:remote_host] = src_ip
      "PROXY OK"
    else
      raise Smtpd500Exception
    end
  end

@TomFreudenberg
Copy link
Member

Iuri,

could you dump the ctx once for both session please in addition?

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Jul 23, 2023

This is ctx right before on_auth_event with haproxy (and on_process_line_unknown_event PROXY handler):

{
  :server=>
    {
      :local_host=>"127.0.0.1", 
      :local_ip=>"127.0.0.1", 
      :local_port=>2525, 
      :local_response=>"smtp-test says welcome!", 
      :remote_host=>"172.17.0.1", 
      :remote_ip=>"172.17.0.1", 
      :remote_port=>"32808", 
      :helo=>"ms-invite.eml", 
      :helo_response=>"smtp-test at your service!", 
      :connected=>2023-07-23 18:01:24.903204 UTC, 
      :exceptions=>0, 
      :errors=>[], 
      :authorization_id=>"", 
      :authentication_id=>"", 
      :authenticated=>"", 
      :encrypted=>2023-07-23 18:01:25.026792 UTC
    }, 
  :envelope=>
    {
    }, 
  :message=>
    {
    }
}

without haproxy:

{
  :server=>
    {
      :local_host=>"127.0.0.1", 
      :local_ip=>"127.0.0.1", 
      :local_port=>2525, 
      :local_response=>"smtp-test says welcome!", 
      :remote_host=>"127.0.0.1", 
      :remote_ip=>"127.0.0.1", 
      :remote_port=>63129, 
      :helo=>"ms-invite.eml", 
      :helo_response=>"smtp-test at your service!", 
      :connected=>2023-07-23 18:02:30.261189 UTC, 
      :exceptions=>0, 
      :errors=>[], 
      :authorization_id=>"", 
      :authentication_id=>"", 
      :authenticated=>"", 
      :encrypted=>2023-07-23 18:02:30.304146 UTC
    },
  :envelope=>
    {
    }, 
  :message=>
    {
    }

@TomFreudenberg
Copy link
Member

Hey Iuri,

thanks for adding all the information.

As a first note and reply:

  1. I would like to add the proxy information as well to ctx in case of a proxy connection
  2. the src_host should be evaluated by DNS if configured like this

But in any case - yes - I like to add the proxy feature as "in library feature".

I prepare an PR and come up with a review to you.

Thanks for that enhancement idea!

Tom

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Jul 23, 2023

Thanks Tom. I like the idea of adding proxy information to context instead of replacing existing info. speaking of adding proxy info - there can be multiple proxies chained together which results adding extra proxy lines (they come in sequence). Just FYI.

Please let me know how I can help with this. I can test the implementation with multiple proxies and check IPs whenever needed.

@TomFreudenberg
Copy link
Member

TomFreudenberg commented Jul 24, 2023

Good evening Iuri,

could you please create a log for a chain of e.g. 3 PROXIES

is it like? :

<<< PROXY TCP4 172.17.5.1 172.17.5.2 55555 5555
>>> PROXY OK

<<< PROXY TCP4 172.17.4.1 172.17.4.2 44444 4444
>>> PROXY OK

<<< PROXY TCP4 172.17.3.1 172.17.3.2 33333 3333
>>> PROXY OK

<<< EHLO ...

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Jul 25, 2023

actually, it is inverted.

below is the log:

Client connect from 127.0.0.1:64748 to 127.0.0.1:2525
>>> 220 smtp-test says welcome!

<<< PROXY TCP4 172.21.0.3 172.21.0.2 48610 3333

>>> PROXY OK
<<< PROXY TCP4 172.21.0.4 172.21.0.3 45420 4444

>>> PROXY OK
<<< PROXY TCP4 172.21.0.1 172.21.0.4 38102 5555

>>> PROXY OK
<<< EHLO ms-invite.eml

172.21.0.4 is listening on 5555
172.21.0.3 is listening on 4444
172.21.0.2 is listening on 3333
172.21.0.1 is running actual server and listening on 2525
172.21.0.1 is also 127.0.0.1 (in docker networking, host shows up as 172.21.0.1)

client (172.21.0.1) -> proxy1 (172.21.0.4:5555) -> proxy2 (172.21.0.3:4444) -> proxy3 (172.21.0.2:3333) -> smtp server (172.21.0.1:2525)

@TomFreudenberg
Copy link
Member

TomFreudenberg commented Jul 25, 2023

Hi Iuri,

thanks for the log.

Just to make it sure:

Client connect from 127.0.0.1:64748 to 127.0.0.1:2525

the to 127.0.0.1:2525 would normally be to 172.21.0.1:2525 but not in your log while MAC only shows the localhost address?

What ist your hosts argument for the instance of MidiSmtpServer? If not already, could you try once hosts: '172.21.0.1'

server = MySmtpd.new(ports: '2525', hosts: '172.21.0.1')

Normally it should also show up the correct ip.

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Jul 25, 2023

the to 127.0.0.1:2525 would normally be to 172.21.0.1:2525 but not in your log while MAC only shows the localhost address?

Since I am using docker on macOS, some network translation is happening when going from within the docker to the host. If the SMTP server was running inside the docker container, then you would not see 127.0.0.1. Basically, host IP is 127.0.0.1 on the macOS side and 172.21.0.1 in the docker containesr - both IPs point to the same host.

What ist your hosts argument for the instance of MidiSmtpServer? If not already, could you try once hosts: '172.21.0.1'

hosts argument is "*". I cannot use 172.21.0.1' (Errno::EADDRNOTAVAIL) since that address does not exist in the host network - docker is using network translation between the virtualization framework and the host. Basically, docker listens on 127.0.0.1:5555 and forwards all packets within the container as if it was coming from 172.21.0.1. 172.21.0.x network only exists inside the docker's virtualization framework.

@TomFreudenberg
Copy link
Member

Got it Iuri, thanks.

@TomFreudenberg
Copy link
Member

Hey Iuri @iuri-gg

I just started with implementation

Here is the comment of the processing:

            # Documentation at https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt
            # syntax
            # PROXY TCP? source-ip dest-ip source-port dest-port
            # supported commands:
            # PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535
            # PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535
            # PROXY UNKNOWN
            # PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535

Question:

When reading documentation we will receive a number of PROXY command lines and allways the given source-ip address should be set as remote_ip.

In case of a chain of PROXY commands the remote_ip is changed until last PROXY line.

Correct?

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 9, 2023

Hey @TomFreudenberg. That is correct - the last PROXY command will have the client's IP as source_ip and it should be set as remote_ip.

Also, I figured that we don't need to return anything for PROXY commands - haproxy passed back all the replies to the client and I was thinking some clients might be more strict and don't like if they receive PROXY OK or something similar, so I changed my implementation of on_process_line_unknown_event to return blank string "" instead of PROXY OK.

@TomFreudenberg
Copy link
Member

Hi @iuri-gg

I would reply the standard SMTP OK signal

return '250 OK'

@TomFreudenberg
Copy link
Member

An this are the possible returns:

            # 250 Requested mail action okay, completed
            # 421 <domain> Service not available, closing transmission channel
            # 500 Syntax error, command unrecognised
            # 501 Syntax error in parameters or arguments

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 10, 2023

I think 250 OK is fine - I will test it and we can go from there

@TomFreudenberg
Copy link
Member

I will merge the feature branch already while we are stil finishing that

TomFreudenberg added a commit that referenced this issue Aug 10, 2023
@TomFreudenberg TomFreudenberg added this to the 3.x.y milestone Aug 11, 2023
@TomFreudenberg
Copy link
Member

TomFreudenberg commented Aug 12, 2023

Hey @iuri-gg

had you the time to check last commit if that fit's now and runs your tests?

For me it is quit ready to push as this implementation is feature ready.

Thanks for your feedback.

Tom

P.S.: Anything to add for the documentation?

  1. https://midi-smtp-server.readthedocs.io/feature_proxy/
  2. https://midi-smtp-server.readthedocs.io/events/#on_proxy_event
  3. https://github.com/4commerce-technologies-AG/midi-smtp-server/blob/master/lib/midi-smtp-server.rb#L239
  4. https://github.com/4commerce-technologies-AG/midi-smtp-server/blob/master/lib/midi-smtp-server.rb#L492-L499

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 12, 2023

Hey @TomFreudenberg. I just tested and both SSL and plain connections worked. However, I found 1 more edge case that might not be a big deal. If I disable pipelining_extension, then I get 500 Bad input, PIPELINING is not allowed error due to the PROXY command being followed with another one. I can get around it by having pipelining_extension enabled.

@TomFreudenberg
Copy link
Member

Hey @iuri-gg

Thanks, I will check that

@TomFreudenberg
Copy link
Member

@iuri-gg that is the involved source:

https://github.com/4commerce-technologies-AG/midi-smtp-server/blob/master/lib/midi-smtp-server.rb#L791

If you could simulate the client once with haproxy proxy between and pipelining disabled. Please just use a telnet session as client and just try

telnet localhost port
# inside session just use
  ehlo test

will that raise pipelining exception as well already?

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 12, 2023

no proxy:

$ telnet localhost 2525
Trying ::1...
Connected to localhost.
Escape character is '^]'.
220 midi-smtp says welcome!
ehlo test
250-midi-smtp at your service!
250-AUTH LOGIN PLAIN
250-STARTTLS
250 OK

3 proxies:

$ telnet localhost 5555
Trying ::1...
Connected to localhost.
Escape character is '^]'.
220 midi-smtp says welcome!
500 Bad input, PIPELINING is not allowed
500 Bad input, PIPELINING is not allowed

server log:

[DEBUG] >>> 220 midi-smtp says welcome!
[ERROR] MidiSmtpServer::Smtpd500PipeliningException (MidiSmtpServer::Smtpd500PipeliningException)
[DEBUG] >>> 500 Bad input, PIPELINING is not allowed
[ERROR] MidiSmtpServer::Smtpd500PipeliningException (MidiSmtpServer::Smtpd500PipeliningException)
[DEBUG] >>> 500 Bad input, PIPELINING is not allowed
[DEBUG] <<< PROXY TCP4 172.18.0.1 172.18.0.3 52036 5555

1 proxy:

$ telnet localhost 3333
Trying ::1...
Connected to localhost.
Escape character is '^]'.
220 midi-smtp says welcome!
ehlo test
250-midi-smtp at your service!
250-AUTH LOGIN PLAIN
250-STARTTLS
250 OK

server log:

[DEBUG] >>> 220 midi-smtp says welcome!
[DEBUG] <<< PROXY TCP4 172.18.0.1 172.18.0.4 51026 3333
[DEBUG] <<< ehlo test

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 12, 2023

when there are multiple proxies chained together, each proxy adds PROXY line, so on initial connection server will get multiple proxy lines back to back before it gets to respond.

@TomFreudenberg
Copy link
Member

Thanks for testing Iuri. I will think about it.

It needs a new command sequence on multiple proxies or enabling pipelining.

Not sure what I like more ...

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 12, 2023

Thanks for testing Iuri. I will think about it.

happy to be of help!

It needs a new command sequence on multiple proxies or enabling pipelining.

Not sure what I like more ...

Yes, I'm not sure either. Is pipelining backward compatible? If clients without pipelining support can still use servers with pipelining enabled, then maybe it should just be enabled all the time on the server.

@TomFreudenberg
Copy link
Member

As you may read here

https://midi-smtp-server.readthedocs.io/appendix_security/

Pipelining allows injection and for that I left it disabled.

So that's why I don't like to enable Pipelining for proxy support

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 12, 2023

@TomFreudenberg I was digging more into the proxy configuration I have set up and I think I have a solution. I think I had wrong proxy configuration - there should only be 1 PROXY command to start with. My configuration for proxy chaining was incorrect - I was missing accept-proxy directive in haproxy for chained proxies.

I added accept-proxy directive to all proxies, except the first one and now I only get 1 PROXY command - the initial one and intermediate servers don't append a new PROXY command.

I believe this is the correct behavior and we should not process more than 1 PROXY command and we should error out if more than 1 PROXY command is received. Otherwise, client can send their own PROXY commands and spoof the IP and prevent IP filtering or rate limiting on the server side.

To summarize, if we only allow 1 PROXY command, we don't have issue with pipelining, we don't have issue with client spoofing IP through PROXY command and haproxy has accept-proxy directive specifically for forwarding PROXY command from upstream proxy.

Sorry for the wild goose chase!

@TomFreudenberg
Copy link
Member

Hey Iuri @iuri-gg

just that I understand you correctly:

There is no chain of Proxy just 1. If getting more than 1 Proxy command, there is a config error in the underlaying system.

Is that the short message?

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 13, 2023

Yes. That’s correct.

@TomFreudenberg
Copy link
Member

Hey Iuri @iuri-gg, I read about the exim implementation and copy the specific content here:

---<<<

from: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-proxies.html

1. Inbound proxies

Exim has support for receiving inbound SMTP connections via a proxy that uses “Proxy Protocol” to speak to it. To include this support, include “SUPPORT_PROXY=yes” in Local/Makefile.

It was built on the HAProxy specification, found at https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt.

The purpose of this facility is so that an application load balancer, such as HAProxy, can sit in front of several Exim servers to distribute load. Exim uses the local protocol communication with the proxy to obtain the remote SMTP system IP address and port information. There is no logging if a host passes or fails Proxy Protocol negotiation, but it can easily be determined and recorded in an ACL (example is below).

Use of a proxy is enabled by setting the hosts_proxy main configuration option to a hostlist; connections from these hosts will use Proxy Protocol. Exim supports both version 1 and version 2 of the Proxy Protocol and automatically determines which version is in use.

The Proxy Protocol header is the first data received on a TCP connection and is inserted before any TLS-on-connect handshake from the client; Exim negotiates TLS between Exim-as-server and the remote client, not between Exim and the proxy server. The Proxy Protocol header must be received within proxy_protocol_timeout, which defaults to 3s.

The following expansion variables are usable (“internal” and “external” here refer to the interfaces of the proxy):

$proxy_external_address IP of host being proxied or IP of remote interface of proxy
$proxy_external_port Port of host being proxied or Port on remote interface of proxy
$proxy_local_address IP of proxy server inbound or IP of local interface of proxy
$proxy_local_port Port of proxy server inbound or Port on local interface of proxy
$proxy_session boolean: SMTP connection via proxy

If $proxy_session is set but $proxy_external_address is empty there was a protocol error. The variables $sender_host_address and $sender_host_port will have values for the actual client system, not the proxy.

--->>>

In case of their (exim) variables they also support only ONE Proxy data information stored for the configuration.

But when I read the original haproxy protocol L779-781

---<<<

Firewall FW1 receives traffic from internet-based clients and forwards it to
reverse-proxy PX1. PX1 adds a PROXY header then forwards to PX2 via FW2. PX2
is configured to read the PROXY header and to emit it on output.

--->>>

I wonder what they mean there by the last part "and to emit it on output". Does ist mean to create a second PROXY command line or does it mean: put the source address from any previous incoming PROXY to output.

Any suggestions on this?

@TomFreudenberg
Copy link
Member

Here is the exim commit for first time experimental implementation of proxy protocol.

https://lists.exim.org/lurker/message/20150505.115516.e01e0a1e.el.html

@TomFreudenberg
Copy link
Member

Maybe we should align with that implementation for PROXY. The only difference I like, would be that the PROXY command in general is optional and not mandytory when enabled. For the reason, that the server could be reachable internal directly under the same internal address.

@TomFreudenberg
Copy link
Member

Hi @iuri-gg

please check updated master and documentation

I guess this should fit now.

Thanks for testing and feedback

Tom

@TomFreudenberg
Copy link
Member

TomFreudenberg commented Aug 13, 2023

To make it clear: following rules exist now for PROXY

if not enabled by proxy_extension:

  1. you can catch it in unknown command

if enabled by proxy_extension

  1. only valid PROXY commands are allowed - all other causes drop of connection (421 and close immediately)
  2. PROXY is optional and not mandatory when enabled
  3. strict checking of parameters - tcp4/6 addresses and ports
  4. only ONE PROXY LINE is allowed - otherwise drop connection
  5. the on_proxy_event may check the connection and also can drop the connection by raising 421
  6. it works with and without pipelining - new special check
  7. if proxy command is later used while already in a session just a sequence error is raised

documented at: https://midi-smtp-server.readthedocs.io/feature_proxy/

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 14, 2023

Thank you @TomFreudenberg. This is great.

@TomFreudenberg
Copy link
Member

TomFreudenberg commented Aug 14, 2023

Hey @iuri-gg

everything ok from your side and ok to close and release it?

Thanks for a short feedback.

Tom

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 15, 2023

Yes @TomFreudenberg - everything looks good. Tested with and without proxy, both pipelining on and off. I also tested that 2 proxy commands raised an error.

@TomFreudenberg
Copy link
Member

Thanks for testing and feedback Iuri

So I close here and proceed for a new release!

Cheers
Tom

@TomFreudenberg
Copy link
Member

Done!

@TomFreudenberg
Copy link
Member

Hey @iuri-gg

please add yourself to the contributors list and send me a PR

https://github.com/4commerce-technologies-AG/midi-smtp-server/blob/master/CONTRIBUTORS.md

Would like to see your credits there!

Thanks for help
Tom

@iuri-gg
Copy link
Contributor Author

iuri-gg commented Aug 16, 2023

Hey @TomFreudenberg. My previous handle was @ocha. I just created PR with my updated GH handle. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants