New FAMILUG

The PyMiers

Tuesday, 16 July 2024

Compile Kotlin có cần JDK không?

Hay "Một mình chống lại cả internet và AI (ChatGPT, Gemini)".

Internet thường hay sai, thậm chí nhiều khi sai nhưng nghe có vẻ đúng đúng, là nền tảng cho sự "ảo tưởng" của các AI chatbot bịa ra đủ thứ và nói rất hay.

build kotlin "Hello world" có cần JDK không?

Với câu hỏi "I want to write kotlin hello world program and build with kotlinc, do I need to install JDK (not JRE)", lên mạng internet tìm câu trả lời:

Ta tìm được StackOverFlow đầy uy tín (và nổi tiếng vì nhiều câu trả lời sai)

Thử hỏi ChatGPT

chatgpt

Và AI của Google: Gemini

gemini

Mọi câu trả lời đều là "có". Nhưng... bài trước Hello Kotlin 2024 hoàn toàn không thấy nhắc tới JDK, vậy có cần không?

JRE là gì


$ apt-cache search openjdk | grep -i jdk-17
openjdk-17-doc - OpenJDK Development Kit (JDK) documentation
openjdk-17-jdk - OpenJDK Development Kit (JDK)
openjdk-17-jdk-headless - OpenJDK Development Kit (JDK) (headless)
openjdk-17-jre - OpenJDK Java runtime, using Hotspot JIT
openjdk-17-jre-headless - OpenJDK Java runtime, using Hotspot JIT (headless)
openjdk-17-jre-zero - Alternative JVM for OpenJDK, using Zero
openjdk-17-source - OpenJDK Development Kit (JDK) source files

Java Runtime Environment (JRE) chứa máy ảo JVM, dùng để chạy các file .class, hay nói chung: để chạy các chương trình trên JVM. JRE tương ứng với chương trình câu lệnh java.

JDK là gì

Java Development Kit (JDK) chứa Java compiler và nhiều thành phần khác cần thiết để viết chương trình chạy trên JVM. JDK có chương trình câu lệnh javac, khi viết bất kì chương trình Java nào đều cần javac để compile thành .class.

Kotlin compiler

Kotlin sử dụng compiler để biến code Kotlin (file .kt) thành Java bytecode (file .class), rồi chạy code đó (interpret) trên máy ảo JVM.

Java và Kotlin là điển hình cho ngôn ngữ dùng cả "compiler" lẫn "interpreter".

Kotlin compiler https://kotlinlang.org/docs/command-line.html được đóng gói thành một file JAR, và người dùng thường nhìn thấy câu lệnh "kotlinc" (thực chất là 1 script).

Kotlinc

kotlinc là một script để gọi câu lệnh java với các option cần thiết:

$ cat $(command -v kotlinc)
#!/usr/bin/env bash
#
...
# Copyright 2011-2015, JetBrains
...
    kotlin_app=("${KOTLIN_HOME}/lib/kotlin-preloader.jar" "org.jetbrains.kotlin.preloading.Preloader" "-cp" "${KOTLIN_HOME}/lib/kotlin-compiler.jar${additional_classpath}" $KOTLIN_COMPILER)
fi

"${JAVACMD:=java}" $JAVA_OPTS "${java_args[@]}" -cp "${kotlin_app[@]}" "${kotlin_args[@]}"

Có thể thấy file kotlin-compiler.jar được dùng ở script trên. Khi chạy kotlinc chỉ thấy gọi lệnh java, không thấy dùng javac (Java compiler).

Build chương trình Kotlin đơn giản dùng stdlib của Java

# main.kt
import java.math.BigInteger

fun pe16(): Int {
    val p: BigInteger = 2.toBigInteger().pow(1000)
    return p.toString()
            .chars()
            .map({ it - '0'.toInt() })
            .sum()
}

fun main() {
    println("tong cac chu so cua 2**1000 la ${pe16()}".toUpperCase())
}

Kết quả

$ kotlinc main.kt
$ kotlin MainKt
TONG CAC CHU SO CUA 2**1000 LA 1366

Hoàn toàn không thấy sử dụng javac trong quá trình này. Trên máy thậm chí không có câu lệnh javac tức không cài JDK.

$ javac
zsh: command not found: javac
$ dpkg -l | grep
ii  openjdk-11-jre:amd64                             11.0.23+9-1ubuntu1~22.04.1                        amd64        OpenJDK Java runtime, using Hotspot JIT
ii  openjdk-11-jre-headless:amd64                    11.0.23+9-1ubuntu1~22.04.1                        amd64        OpenJDK Java runtime, using Hotspot JIT (headless)

Kết luận

Build chương trình kotlin không cần JDK.

Sau khi lên kotlinlang Slack để kiểm tra lại câu trả lời, một người dùng Kotlin lâu năm đã cùng sửa lại internet với câu trả lời StackOverFlow https://stackoverflow.com/a/78750618/807703.

Kết luận

Thời buổi AI 4.0, nhớ giành thời gian tự suy nghĩ.

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Saturday, 13 July 2024

Hello Kotlin 2024

Kotlin là gì

kotlin

$ apt-cache show kotlin
Package: kotlin
Architecture: all
Version: 1.3.31+~1.0.1+~0.11.12-2
...
Installed-Size: 286240
...
Homepage: https://kotlinlang.org
Description-en: cross-platform, general-purpose programming languagehttps://kotlinlang.org/
 Kotlin is a cross-platform, statically typed, general-purpose programming
 language with type inference. Kotlin is designed to interoperate fully with
 Java, and the JVM version of its standard library  depends on the Java Class
 Library, but type inference allows its syntax to be more concise.

Kotlin https://kotlinlang.org/ là một ngôn ngữ lập trình static-typing hiện đại (2011) phát triển bởi JetBrain (IDE: PyCharm, IntelliJ IDEA), chạy trên máy ảo JVM (của Java), tương thích với Java (dùng được thư viện Java), cú pháp ngắn gọn, hỗ trợ functional programming, được Google hỗ trợ là ngôn ngữ chính thức để viết app Android.

Tại sao dùng Kotlin?

Để hiểu 1 hệ thống đầy đủ, lập trình viên cần hiểu từ frontend (FE) tới backend (BE). Lập tức nghĩ rằng JavaScript FE + NodeJS BE là câu trả lời? Câu trả lời này đúng, nhưng không phải duy nhất. Ngày nay, đa phần các sản phẩm đều có frontend là mobile app, nên app Android hay iOS cũng là các lựa chọn cho frontend, nhiều ứng dụng thậm chí không có hoặc có mà ít dùng web frontend (Grab/Uber/Tiktok/Momo). Ngoài ra các hệ thống doanh nghiệp đa số sử dụng Java hay .NET cho BE chứ không phải NodeJS. Khiến cho các option là:

  • Java/Kotlin BE, Java/Kotlin mobile FE
  • BE nào đó, Object-C/Swift iOS mobile FE
  • BE nào đó, JavaScript web FE
  • BE nào đó, ReactNative/Flutter mobile FE

Java/Kotlin là option ngon lành nhất ở đây để làm cả FE lẫn BE.

Cài đặt

$ sudo apt install -y kotlin
...
Setting up kotlin (1.3.31+~1.0.1+~0.11.12-2) ...

Viết hello world

Viết code vào file main.kt:

fun main() {
    println("Hello FAMILUG 2024!")
}

Build source code thành file MainKt.class

$ kotlinc main.kt
$ kotlin MainKt
Hello FAMILUG 2024!
$ find .
.
./META-INF
./META-INF/main.kotlin_module
./MainKt.class
./main.kt

$ file MainKt.class
MainKt.class: compiled Java class data, version 50.0 (Java 1.6)

$ xxd MainKt.class| head -n3
00000000: cafe babe 0000 0032 002b 0100 064d 6169  .......2.+...Mai
00000010: 6e4b 7407 0001 0100 106a 6176 612f 6c61  nKt......java/la
00000020: 6e67 2f4f 626a 6563 7407 0003 0100 046d  ng/Object......m

8 bytes đầu đáng yêu của file .class: "cafe babe".

Chạy file MainKt

$ /usr/bin/time -v kotlin MainKt
Hello FAMILUG 2024!
    Command being timed: "kotlin MainKt"
    User time (seconds): 0.08
    System time (seconds): 0.02
    Percent of CPU this job got: 116%
    Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.10
    Average shared text size (kbytes): 0https://github.com/JetBrains/kotlin/releases/tag/v1.3.31
    Average unshared data size (kbytes): 0
    Average stack size (kbytes): 0
    Average total size (kbytes): 0
    Maximum resident set size (kbytes): 37888
    Average resident set size (kbytes): 0
    Major (requiring I/O) page faults: 0
    Minor (reclaiming a frame) page faults: 5296
    Voluntary context switches: 282
    Involuntary context switches: 13
    Swaps: 0
    File system inputs: 0
    File system outputs: 64
    Socket messages sent: 0
    Socket messages received: 0
    Signals delivered: 0
    Page size (bytes): 4096
    Exit status: 0

Dùng tới ~37 MiB, gấp 4 lần RAM so với Python, nhưng không sao, năm 2024 thì 40MB RAM không là gì so với Google Chrome hay các IDE cả.

$ /usr/bin/time -v /usr/bin/python3.11 -c 'print("Hello FAMILUG!")' |& grep Maximum
    Maximum resident set size (kbytes): 8960

Viết vài chương trình đơn giản

Viết function tính tổng 3 số

fun sum3(a: Int, b: Int, c: Int): Int {
    return a + b + c
}

fun main() {
    println("Hello FAMILUG 2024!")
    val sum = sum3(1,2,3)
    println("Tong cua 1+2+3 la ${sum}")
}

Kết quả:

$ kotlinc main.kt
$ kotlin MainKt
Hello FAMILUG 2024!
Tong cua 1+2+3 la 6

Không khác mấy code Python3

def sum3(a: int, b: int, c: int) -> int:
    return a + b +c

def main():
    print("Hello FAMILUG 2024!")
    sum = sum3(1,2,3)
    print(f"Tong cua 1+2+3 la {sum}")

main()

Giải bài ProjectEuler 1 - tổng các số nhỏ hơn 1000 chia hết cho 3 hoặc 5

# main.kt
fun pe01(): Int {
    var sum = 0
    for (i in 1..999) {
        if (i % 3 == 0 || i % 5 == 0) {
            sum += i
        }
    }
    return sum
}

fun pe01_functional(): Int {
    return (1..999)
        .filter({it -> it % 3 == 0 || it % 5 == 0}) // hoặc ngắn hơn: .filter({ it % 3 == 0 || it % 5 == 0 })
        .sum()
}


fun main() {
    println(pe01())
    println(pe01() == pe01_functional())
}

Kết quả

$ kotlinc main.kt
hvn@mini:kotlinplay $ kotlin MainKt
233168
true

Giải bài ProjectEuler 16 - tổng các chữ số của 2 mũ 1000

# main.kt
import java.math.BigInteger

fun pe16(): Int {
    val p: BigInteger = 2.toBigInteger().pow(1000)
    return p.toString()
            .chars()
            .map({ it - '0'.toInt() })
            .sum()
}

fun main() {
    println("tong cac chu so cua 2**1000 la ${pe16()}".toUpperCase())
}

Kết quả

$ kotlinc main.kt
$ kotlin MainKt
TONG CAC CHU SO CUA 2**1000 LA 1366

String functions: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/ Char functions: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-char/

Đọc file /etc/passwd và tìm max uid trong các user

Dùng sort command để so sánh kết quả:

$ sort -nk 3 /etc/passwd | head -n3
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
avahi-autoipd:x:110:119:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin
avahi:x:114:121:Avahi mDNS daemon,,,:/run/avahi-daemon:/usr/sbin/nologin

Code Kotlin

import java.io.File
import java.io.InputStream

fun main() {
    val inputStream: InputStream = File("/etc/passwd").inputStream()
    val lineList = mutableListOf<Int>()

    inputStream.bufferedReader().forEachLine(
        { it -> lineList.add(it.split(":")[2].toInt()) }
    )
    println(lineList.max())
}

Kết quả

$ kotlinc main.kt
$ kotlin MainKt
65534

Tham khảo

Kotlin tour: https://kotlinlang.org/docs/kotlin-tour-hello-world.html

Tổng kết

Bài này giới thiệu Kotlin và viết các chương trình đơn giản, sử dụng các thư viện standard/thư viện có sẵn của Java, đồng thời dùng bản Kotlin có sẵn trên Ubuntu 22.04 đã khá cũ (1.3.31 từ 2019) bản hiện tại là Kotlin 2.0, build và chạy code trực tiếp với kotlinc trên câu lệnh CLI mà không dùng IDE hay build tool.

Bài viết sau sẽ giới thiệu build tool, sử dụng kotlin bản mới nhất và các thư viện tải từ trên mạng.

Kết luận

Kotlin ngắn gọn, hiện đại, viết được app di động lẫn code backend/frontend. Ngon không thể bỏ phi.

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Friday, 12 July 2024

Chơi CTF giúp gì cài OpenSearch?

OpenSearch vs ElasticSearch

OpenSearch là 1 bản fork của Amazon từ ElasticSearch, sau vụ đổi license đình đám từ công ty Elastic đứng sau ElasticSearch không muốn Amazon thu lời từ sản phẩm open-source của họ. OpenSearch ngày càng khác biệt với ElasticSearch, mang theo những ưu / nhược điểm riêng.

Một nhược điểm là nó fork từ bản cũ của ElasticSearch, nên nhiều lỗi còn rất khó đọc. Bài này nhờ kỹ năng chơi CTF với team PyMi mà giải quyết một vấn đề đau đầu.

Một ưu điểm là OpenSearch hỗ trợ giải pháp login doanh nghiệp bằng SSO: SAML, OIDC, LDAP... còn ElasticSearch phải trả phí mua license.

Chạy 1 node trên máy bằng podman (docker)

podman là 1 phần mềm phát triển bởi RedHat thay thế cho docker, sau khi docker đổi qua tính phí trên Desktop (DockerDesktop), podman dần trở nên phổ biến. Câu lệnh podman tương thích với docker nên chỉ cần thay chữ podman trong bài thành docker là được. Bài này chạy bằng podman trên máy để bạn đọc có thể làm theo, thực tế chạy trên 1 K8S cluster

Chạy 1 container opensearch theo hướng dẫn tại trang dockerhub, thấy rất nhiều log:

$ podman run -it -p 9200:9200 -p 9600:9600 -e OPENSEARCH_INITIAL_ADMIN_PASSWORD=daemonH4gx@4 -e "discovery.type=single-node" --name opensearch-node docker.io/opensearchproject/opensearch:2.15.0

Enabling OpenSearch Security Plugin
Enabling execution of install_demo_configuration.sh for OpenSearch Security Plugin
OpenSearch 2.12.0 onwards, the OpenSearch Security Plugin a change that requires an initial password for 'admin' user.
Please define an environment variable 'OPENSEARCH_INITIAL_ADMIN_PASSWORD' with a strong password string.
If a password is not provided, the setup will quit.
 For more details, please visit: https://opensearch.org/docs/latest/install-and-configure/install-opensearch/docker/
### OpenSearch Security Demo Installer
### ** Warning: Do not use on production or public reachable systems **
OpenSearch install type: rpm/deb on Linux 6.5.0-41-generic amd64
OpenSearch config dir: /usr/share/opensearch/config/
OpenSearch config file: /usr/share/opensearch/config/opensearch.yml
OpenSearch bin dir: /usr/share/opensearch/bin/
OpenSearch plugins dir: /usr/share/opensearch/plugins/
OpenSearch lib dir: /usr/share/opensearch/lib/
Detected OpenSearch Version: 2.15.0
Detected OpenSearch Security Version: 2.15.0.0
Admin password set successfully.
### Success
...
[2024-07-12T13:16:30,697][INFO ][o.o.s.c.ConfigurationRepository] [00a159b488ac] Node '00a159b488ac' initialized

server đã mặc định setup sẵn SSL/TLS port 9200.

Sau khi cài lên trên K8S cluster, bỗng dưng xuất hiện hàng đống log error như sau

[2024-07-12T13:16:36,456][ERROR][o.o.h.n.s.SecureNetty4HttpServerTransport] [00a159b488ac] Exception during establishing a SSL connection: io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record: 474554202f20485454502f312e310d0a486f73743a206c6f63616c686f73743a393230300d0a557365722d4167656e743a206375726c2f372e38312e300d0a4163636570743a202a2f2a0d0a0d0a
io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record: 474554202f20485454502f312e310d0a486f73743a206c6f63616c686f73743a393230300d0a557365722d4167656e743a206375726c2f372e38312e300d0a4163636570743a202a2f2a0d0a0d0a
    at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1314) ~[netty-handler-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1387) ~[netty-handler-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) ~[netty-codec-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[netty-codec-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysPlain(NioEventLoop.java:689) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:652) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) [netty-common-4.1.110.Final.jar:4.1.110.Final]
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-common-4.1.110.Final.jar:4.1.110.Final]
    at java.base/java.lang.Thread.run(Thread.java:1583) [?:?]

dễ dàng tìm thấy lỗi này trên mạng

nhưng ai gửi request http đến địa chỉ này?

Tìm ai gửi request

Các phương án:

  • cài tcpdump, ngrep để dump traffic pod 9200 xem nội dung traffic, không dễ cài được package nếu image đã remove các package manager (như dùng distroless images)
  • đọc log?! ai đọc stacktrace Java???

Giải pháp là đọc log, phần nội dung bí hiểm trong log message:

474554202f20485454502f312e310d0a486f73743a206c6f63616c686f73743a393230300d0a557365722d4167656e743a206375726c2f372e38312e300d0a4163636570743a202a2f2a0d0a0d0a

chứa dãy 45 47 55 ... các ký tự từ a-e từ 0-9... với kinh nghiệm chơi các bài dễ trong các giải CTF, đoán ngay dùng hex https://n.pymi.vn/base16.html để xem là gì. Bật Python:

>>> s = "474554202f20485454502f312e310d0a486f73743a206c6f63616c686f73743a393230300d0a557365722d4167656e743a206375726c2f372e38312e300d0a4163636570743a202a2f2a0d0a0d0a"
>>> bytes.fromhex(s)
b'GET / HTTP/1.1\r\nHost: localhost:9200\r\nUser-Agent: curl/7.81.0\r\nAccept: */*\r\n\r\n'

đọc được nội dung của request đã gửi tới server.

Trên thực tế, đống log có User-Agent là 1 chương trình monitoring, từ đó tìm ra và tắt nó đi.

PS: có thể dùng https://gchq.github.io/CyberChef/ để decode hex.

Kết luận

Chơi CTF không bổ ngang thì bổ dọc, không bổ dọc thì lại bổ ngang.

Thực hiện trên:

$ lsb_release -d
Description:    Ubuntu 22.04.4 LTS

$ podman --version
podman version 3.4.4

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺